Build a Bookmark Manager with Copilot
Use GitHub Copilot Agent Mode to build a full-stack bookmark manager app.
Introduction
30 Second Summary
Every developer has dozens of browser tabs open, scattered bookmarks across devices, and no clean way to organize them. Now imagine building your own solution to that problem, with an AI writing most of the code for you.
In this project, you will build a full-stack Bookmark Manager app using GitHub Copilot's Agent Mode. You will learn the professional workflow that developers use to let AI scaffold, build, and refine multi-file applications autonomously.
What You'll Build
A Bookmark Manager web application with an Express.js REST API backend and a React frontend, built primarily through AI-assisted coding with GitHub Copilot Agent Mode.
By the end of this project, you'll have:
- A working Express.js API with endpoints for creating, reading, and deleting bookmarks, stored in a local JSON file.
- A React frontend that displays bookmarks, lets you add new ones, and filter by tag.
- A custom instructions file (.github/copilot-instructions.md) that shapes how Copilot generates code for your project.
- Secret Mission: Add a real-time search feature that filters bookmarks by title and URL as you type.
Are there any prerequisites?
You should have an intermediate understanding of JavaScript, HTML/CSS, and basic Git usage. You do not need prior experience with GitHub Copilot or AI coding assistants.
Before We Start
You're about to build a full-stack Bookmark Manager app using GitHub Copilot Agent Mode. Before writing any code, you need the right tools installed and configured.
In this step, you'll install Node.js (the JavaScript runtime that powers your backend), activate Copilot Free in VS Code, and confirm that Agent Mode is ready to use.
In this step, get ready to:
- Install and verify Node.js v24 LTS.
- Activate GitHub Copilot Free in VS Code.
- Verify Agent Mode is accessible in the Copilot Chat panel.
Install Node.js and verify
Node.js is the JavaScript runtime your backend server will run on. It also comes bundled with npm, the package manager you'll use to install project dependencies.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code and press Enter to open it.
- Open the integrated terminal by clicking Terminal in the top menu bar, then clicking New Terminal.
- Check if Node.js is already installed by running this command:
node --version
Look at the output in your terminal and pick the tab below that matches what you see.
✔️ I see version 24 or higher
Node.js is ready. Now verify that npm is installed by running:
npm --version
You should see a version starting with 11 (e.g. 11.x.x). Both tools are ready to go.
ⓧ I see an older version
You have Node.js installed, but it's an older version. This project requires v24 or higher for compatibility with the tools you'll use.
- Open your browser and go to https://nodejs.org.
- Click the LTS download button (currently v24.18.0). The site detects your operating system automatically.
- Run the downloaded installer and follow the prompts. Accept all default options.
- Close and reopen VS Code so the terminal picks up the new version.
- Run node --version again to confirm it now shows v24.x.x.
- Run npm --version to confirm you see v11.x.x.
Still seeing the old version?
Some systems have multiple Node.js installations. Make sure you fully quit VS Code (not just close the window) and reopen it. The terminal needs to reload its PATH to find the new version.
Help me troubleshoot my Node.js version
ⓧ Command not found
Node.js is not installed yet. Let's fix that.
- Open your browser and go to https://nodejs.org.
- Click the LTS download button (currently v24.18.0). The site detects your operating system automatically.
- Run the downloaded installer and follow the prompts. Accept all default options.
- Close and reopen VS Code so the terminal picks up the new installation.
- Run node --version to confirm it shows v24.x.x.
- Run npm --version to confirm you see v11.x.x.
Install not working?
Make sure you download the LTS version (not Current). If the installer fails, try running it as administrator (Windows) or check that you have enough disk space.
Help me install Node.js
What is Node.js?
Node.js lets you run JavaScript outside of a web browser. Your Bookmark Manager's backend server will be a Node.js application that handles API requests and stores data.
npm (Node Package Manager) comes bundled with Node.js. You'll use it to install libraries like Express.js that make building APIs faster.
Activate GitHub Copilot Free in VS Code
GitHub Copilot is an AI coding assistant that lives inside VS Code. The Free plan gives you inline code suggestions and chat-based help at no cost.
- In VS Code, click the Copilot icon in the top right corner of the window.
- Click Sign up for Copilot Free in the sidebar that appears.
- Sign in with your GitHub account when prompted.
What happens during sign-in?
VS Code will open a browser window for GitHub authentication. After you authorize, VS Code automatically installs the required Copilot extensions. You do not need to download or install them manually.
- Verify Copilot is working by creating a new file. Click File in the top menu, then New File.
- Start typing some code (e.g. function hello) and confirm you see grey inline suggestions appear.
If you see suggestions, Copilot is active. You can close this test file without saving.
Not seeing inline suggestions?
Make sure your file has a recognized extension like .js or .py. Copilot needs a file type to activate. If suggestions still do not appear, check the Copilot icon in the status bar at the bottom of VS Code. A spinning icon means it's loading. A crossed-out icon means it's disabled for this file type.
Help me activate Copilot
Verify Agent Mode is accessible
Agent Mode is Copilot's most powerful feature. It can autonomously edit multiple files, run terminal commands, and iterate on errors to complete complex tasks.
- Open the Chat view by selecting Chat in the VS Code title bar, then selecting Open Chat.
- At the top of the chat panel, find the mode dropdown. It defaults to Ask.
- Click the dropdown and switch it to Agent.
If you see Agent as an option in the dropdown, you're all set. This is the mode you'll use throughout this project to build your app.
What are the three Copilot Chat modes?
Copilot Chat has three modes. Ask answers questions about your code without making changes. Agent autonomously edits files and runs commands to complete tasks. Plan creates a detailed implementation strategy before any code changes happen.
You'll use all three modes in this project, starting with Agent Mode for the heavy lifting.
Don't see Agent as an option?
Make sure you completed the Copilot sign-in step above. Agent Mode requires an active Copilot subscription (including the Free plan). Try reloading VS Code by pressing Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows), then typing Reload Window and pressing Enter.
Help me find Agent Mode
Your environment is ready. Next up, you'll create your project folder, initialize Git, and write a custom instructions file that tells Copilot exactly how you want it to write code.
Set Up Your Project and Custom Instructions
Your environment is ready. Now it's time to create the project itself and teach GitHub Copilot how you want it to write code.
Copilot reads a special file called .github/copilot-instructions.md at the root of your repository. Whatever conventions you define there shape every inline suggestion and chat response Copilot gives you.
In this step, get ready to:
- Create the project folder and initialize a Git repository.
- Write a custom instructions file that configures Copilot for your stack.
- Commit your project foundation.
Create the project folder and initialize Git
- In VS Code, open the integrated terminal by pressing Ctrl+` (backtick key, next to the 1 key).
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder by running this command:
mkdir bookmark-manager
- Confirm the folder exists by running:
ls
You should see bookmark-manager listed in the output.
- Now open this folder in VS Code:
- Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the bookmark-manager folder, and click Open.
Why open the folder this way?
Opening a folder in VS Code makes it the workspace root. The integrated terminal will start in this directory, and Copilot will use it as the project boundary when reading your custom instructions file.
- Open the integrated terminal in the new workspace by pressing Ctrl+` again.
- Initialize a Git repository by running:
git init
You should see a message that says Initialized empty Git repository.
- Create the .github folder by running:
mkdir .github
- In the VS Code Explorer sidebar on the left, right-click the .github folder.
- Click New File.
- Type copilot-instructions.md and press Enter.
The file opens automatically in the editor. You should see copilot-instructions.md nested inside the .github folder in the Explorer sidebar.
Don't see the .github folder in Explorer?
Folders starting with a dot are sometimes hidden by default. Make sure you ran mkdir .github in the terminal while inside the bookmark-manager folder. You can confirm by running ls -a to see hidden folders.
Help me find my .github folder in VS Code
Write your custom instructions file
This file tells Copilot about your project's tech stack, coding conventions, and folder structure. Copilot reads it automatically for every suggestion it makes across the entire project.
- In the copilot-instructions.md file that opened in the editor, paste the following content:
## Project Overview
This is a Bookmark Manager app with an Express.js backend and a React + Vite frontend.
## Tech Stack
- Backend: Node.js with Express.js (ES modules)
- Frontend: React with Vite
- Data storage: Local JSON file (no database)
## Code Style
- Use ES modules (import/export) everywhere
- Use async/await over promise chains
- Use functional React components with hooks
- Use descriptive variable and function names
## Project Structure
- server/ - Express.js backend API
- client/ - React + Vite frontend
- .github/ - GitHub and Copilot configuration
## Conventions
- API routes follow RESTful patterns
- Error responses include a JSON body with an "error" field
- React components are one component per file
- CSS uses module-scoped styles
What does this file do?
Each section gives Copilot specific guidance:
- Tech Stack tells Copilot which frameworks to use (Express.js backend, React + Vite frontend) so it never suggests incompatible libraries.
- Code Style ensures Copilot uses ES modules and async/await instead of older patterns like CommonJS or raw promises.
- Project Structure maps your folder layout so Copilot puts generated files in the right places.
- Conventions defines patterns like RESTful routes and one-component-per-file that Copilot will follow automatically.
- Save the file by pressing Cmd+S (macOS) or Ctrl+S (Windows).
✔️ 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 is the complete .github/copilot-instructions.md file:
## Project Overview
This is a Bookmark Manager app with an Express.js backend and a React + Vite frontend.
## Tech Stack
- Backend: Node.js with Express.js (ES modules)
- Frontend: React with Vite
- Data storage: Local JSON file (no database)
## Code Style
- Use ES modules (import/export) everywhere
- Use async/await over promise chains
- Use functional React components with hooks
- Use descriptive variable and function names
## Project Structure
- server/ - Express.js backend API
- client/ - React + Vite frontend
- .github/ - GitHub and Copilot configuration
## Conventions
- API routes follow RESTful patterns
- Error responses include a JSON body with an "error" field
- React components are one component per file
- CSS uses module-scoped styles
Commit the instructions file
Committing this file to Git means Copilot will reference these instructions every time you open the project.
- Stage and commit the instructions file by running these commands in the terminal:
git add .
git commit -m "Add Copilot custom instructions"
What do these commands do?
- git add . stages all new and changed files so Git knows to include them in the next commit.
- git commit -m "..." saves a snapshot of your staged files with a descriptive message.
You should see output confirming the commit, including the message and the number of files changed.
Seeing an error about user identity?
Git needs to know who you are before it can create a commit. Run the following commands with your own name and email:
git config --global user.name "Your Name"
git config --global user.email "your-email@example.com"
Then try the git commit command again.
Help me fix my Git identity error
Your project foundation is set. Next up, you'll use Agent Mode to build the entire Express.js backend API in one go.
Build the Backend API with Agent Mode
Your project foundation is in place. The custom instructions file you committed tells Copilot exactly how you want your code structured, from ES modules to RESTful conventions.
Now it's time to see Agent Mode do what it does best. You'll write one detailed prompt and watch Copilot autonomously create multiple files, wire them together, and even run terminal commands to set up your entire Express.js backend API.
In this step, get ready to:
- Use Agent Mode to scaffold a full Express.js API with CRUD routes.
- Review and refine the generated backend code.
- Test the API with a curl command and commit your work.
Use Agent Mode to scaffold the Express API
Agent Mode can create entire project structures from a single prompt. The more specific your prompt, the closer Copilot's output will match what you need.
- Return to the Copilot Chat panel by pressing Cmd+Shift+I (macOS) or Ctrl+Shift+I (Windows).
- At the top of the chat panel, confirm the mode dropdown is set to Agent.
- Type this prompt into the chat input and press Enter:
Create an Express.js backend API in a server/ directory. Include:
- A package.json with type: module, express and cors dependencies, and start/dev scripts
- An index.js entry point running on port 3001 with cors and JSON middleware
- A routes/bookmarks.js file with GET /, POST /, and DELETE /:id endpoints
- A data/bookmarks.json file initialized as an empty array
Each bookmark should have: id (timestamp string), url, title, tags (array of strings), and createdAt (ISO string). Store bookmarks by reading and writing the JSON file with fs. Validate that POST requests include url and title.
Why be this specific in a prompt?
Copilot Agent Mode works best when you name exact file paths, data shapes, and requirements upfront. Vague prompts produce vague code. Your copilot-instructions.md already tells Copilot your conventions, so this prompt fills in the specifics of what to build.
- Copilot will present a plan showing which files it intends to create. Review the list to confirm it includes server/package.json, server/index.js, server/routes/bookmarks.js, and server/data/bookmarks.json.
- Click Accept (or press Enter) to approve the plan and let Copilot create the files.
- If Copilot asks permission to run terminal commands (like creating directories), click Allow to let it proceed.
What is Agent Mode doing behind the scenes?
Agent Mode reads your copilot-instructions.md for context, decides which files to create, writes the code, and can even run shell commands. It iterates on its own errors automatically.
This is different from Ask mode (which just answers questions) or inline suggestions (which complete one line at a time). Agent Mode handles entire multi-file tasks end-to-end.
Review and refine the generated code
Copilot's output is a starting point, not a finished product. Professional developers always review AI-generated code against their requirements.
- Open server/index.js in the editor. Confirm it imports express, cors, and the bookmark routes, then listens on port 3001.
- Open server/routes/bookmarks.js. Verify it has three RESTful routes: GET /, POST /, and DELETE /:id.
- Check that the POST route validates url and title are present, and that each bookmark includes a tags array field.
- Open server/package.json and confirm it has "type": "module" for ES module support.
Refine with follow-up prompts
If anything is missing (for example, the tags field or the cors middleware), type a follow-up prompt in Agent mode describing the change. For example: "Add a tags field to each bookmark as an array of strings" or "Add cors middleware to index.js". Copilot will iterate on its own output.
Here is what each file should look like. Compare your generated code against these references.
✔️ Awesome, I've got everything!
Great. Make sure you've saved all the files in your server/ directory before moving on.
ⓧ I'd like to double check the full code
Here are all four server files. If your Copilot output differs, use follow-up prompts to adjust, or replace the file contents with these references.
{
"name": "bookmark-manager-server",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "node --watch index.js"
},
"dependencies": {
"cors": "^2.8.5",
"express": "^4.19.2"
}
}
import express from 'express';
import cors from 'cors';
import bookmarkRoutes from './routes/bookmarks.js';
const app = express();
const PORT = 3001;
app.use(cors());
app.use(express.json());
app.use('/bookmarks', bookmarkRoutes);
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
import { Router } from 'express';
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const router = Router();
const __dirname = dirname(fileURLToPath(import.meta.url));
const DATA_FILE = join(__dirname, '..', 'data', 'bookmarks.json');
function getBookmarks() {
if (!existsSync(DATA_FILE)) {
return [];
}
const data = readFileSync(DATA_FILE, 'utf-8');
return JSON.parse(data);
}
function saveBookmarks(bookmarks) {
const dir = dirname(DATA_FILE);
if (!existsSync(dir)) {
import('fs').then(fs => fs.mkdirSync(dir, { recursive: true }));
}
writeFileSync(DATA_FILE, JSON.stringify(bookmarks, null, 2));
}
router.get('/', (req, res) => {
const bookmarks = getBookmarks();
res.json(bookmarks);
});
router.post('/', (req, res) => {
const { url, title, tags } = req.body;
if (!url || !title) {
return res.status(400).json({ error: 'URL and title are required' });
}
const bookmarks = getBookmarks();
const newBookmark = {
id: Date.now().toString(),
url,
title,
tags: tags || [],
createdAt: new Date().toISOString()
};
bookmarks.push(newBookmark);
saveBookmarks(bookmarks);
res.status(201).json(newBookmark);
});
router.delete('/:id', (req, res) => {
const bookmarks = getBookmarks();
const filtered = bookmarks.filter(b => b.id !== req.params.id);
if (filtered.length === bookmarks.length) {
return res.status(404).json({ error: 'Bookmark not found' });
}
saveBookmarks(filtered);
res.status(204).send();
});
export default router;
[]
Test the API
Your code exists on disk, but you haven't proven it actually runs. Let's install the dependencies, start the server, and hit it with a real request.
- Install the backend dependencies by running these commands in your terminal:
cd server
npm install
What does this do?
The cd server command moves you into the server directory. Then npm install reads package.json and downloads the express and cors packages into a node_modules folder.
- You should see output listing the installed packages with no errors.
- Start the Express server by running:
npm start
- You should see Server running on http://localhost:3001 printed in the terminal.
Don't see the server running message?
- Make sure you ran npm install before npm start. Without installing dependencies first, Node.js cannot find the express package.
- Check that your server/package.json has "type": "module". Without this, Node.js will not understand import statements.
- Confirm your terminal is inside the server/ directory, not the project root.
Help me debug why my Express server won't start.
Now let's send a real request to the API. You'll use Copilot in Ask mode to generate a test command, which is a great example of switching modes for different tasks.
- Open a second terminal in VS Code by clicking the + icon in the terminal panel.
- Switch the Copilot Chat mode dropdown from Agent to Ask.
- Type: "Give me a curl command to POST a new bookmark to http://localhost:3001/bookmarks with a url, title, and tags array."
- Copy the curl command Copilot gives you and run it in your new terminal. It should look similar to this:
curl -X POST http://localhost:3001/bookmarks \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "title": "Example Site", "tags": ["test"]}'
What should I see?
The API should return a JSON response with your new bookmark, including an auto-generated id and createdAt timestamp. This confirms the POST route is working and data is being saved to bookmarks.json.
- Verify the GET route works too by running:
curl http://localhost:3001/bookmarks
- You should see an array containing the bookmark you just created.
- Stop the server by pressing Ctrl+C in the first terminal (the one running the server).
- Move back to the project root and commit your backend code by running:
cd ..
git add .
git commit -m "Add Express backend API with bookmark CRUD routes"
Why commit after testing?
Committing working code creates a checkpoint you can return to. If a future Copilot prompt produces unexpected changes, you can always git diff against this commit to see what changed.
Your backend API is live and tested. Next up, you'll use Agent Mode again to build a React frontend that connects to this API and displays your bookmarks in a real UI.
Build the React Frontend with Agent Mode
Your backend API is running and ready to serve bookmark data. Now you need a frontend that lets users actually see, add, and manage their bookmarks in a browser.
In this step, you'll use Agent Mode to scaffold a React app, build the UI components, and wire everything up to your Express API.
In this step, get ready to:
- Scaffold a React + Vite app in the client/ directory.
- Build bookmark list, add form, and tag filter components.
- Connect the frontend to the backend API and verify the full app works.
Use Agent Mode to scaffold the React app
Before Copilot can build your UI, it needs a React project to work inside. You'll prompt Agent Mode to scaffold a fresh Vite app with the React template.
- Switch back to the Copilot Chat panel in VS Code.
- Confirm the mode dropdown at the top of the chat is set to Agent.
- Type the following prompt into the chat:
Create a React frontend in a client/ directory using Vite with the React template. Install the dependencies and make sure the dev server starts successfully.
What should I see?
Agent Mode reads your .github/copilot-instructions.md file and understands your project structure. It should propose running npm create vite@latest client -- --template react (or a similar scaffold command), then install dependencies with npm install.
Copilot will ask for permission before running terminal commands. Approve each one.
- Approve the terminal commands Copilot suggests.
- Once Copilot confirms the scaffold is complete, verify the dev server starts by navigating to the client/ directory and running:
cd client
npm run dev
- Open the URL shown in the terminal (usually http://localhost:5173) in your browser. You should see the default Vite + React welcome page.
- Stop the dev server by pressing Ctrl+C in the terminal.
Dev server not starting?
If Copilot scaffolded the project but npm run dev fails, make sure you are inside the client/ folder. Run npm install to ensure dependencies installed correctly.
If Copilot generated a different folder structure, ask it: "Move the React app into a client/ directory at the project root."
Help me debug my Vite dev server
Use Agent Mode to build the bookmark UI
A default Vite app is just a starting point. You need components that display bookmarks, let users add new ones, and filter by tag. Agent Mode can create all of these across multiple files in one go.
- Return to the Copilot Chat panel (still in Agent mode).
- Type the following prompt:
Replace the default App component with a Bookmark Manager UI. Create these components in client/src/components/:
1. BookmarkList - displays bookmarks fetched from GET /bookmarks. Each item shows the title as a link, the URL, tags as small badges, and a Delete button.
2. AddBookmarkForm - a form with inputs for URL, title, and tags (comma separated). On submit, POST to /bookmarks.
3. TagFilter - a sidebar that shows all unique tags and lets the user filter bookmarks by clicking a tag.
In App.jsx, manage state for bookmarks, selectedTag, loading, and error. Fetch bookmarks on mount. Filter bookmarks by the selected tag before passing them to BookmarkList. Add CSS styles in App.css for a clean layout.
What does Agent Mode do with this?
Agent Mode analyzes your prompt and determines which files to create and edit. It will create BookmarkList.jsx, AddBookmarkForm.jsx, and TagFilter.jsx in client/src/components/.
It will also rewrite App.jsx and App.css to wire everything together. This multi-file coordination is where Agent Mode shines compared to simple inline suggestions.
- Review the changes Copilot proposes. Check that it creates three separate component files.
- Approve any terminal commands Copilot suggests (such as creating the components/ folder).
- If Copilot's code uses a fetch library instead of the built-in fetch API, ask it: "Use the built-in fetch API instead of a third-party library."
Check the component structure
Each component should be in its own file under client/src/components/. If Copilot put everything in App.jsx, ask it: "Split the components into separate files under src/components/."
Copilot generated different code than expected?
Agent Mode may produce slightly different code each time. The important things to verify are that App.jsx manages bookmark state, fetches data from /bookmarks, and renders the three child components.
Use the double-check tab at the end of this step to compare your code with the expected version. If there are major differences, you can paste the expected code directly.
Help me fix my Copilot-generated components
Connect frontend to backend and test
Your React app currently fetches from /bookmarks, but that path doesn't point anywhere until you configure a dev server proxy. The proxy forwards API requests from the Vite dev server to your Express backend.
- In the Copilot Chat panel (Agent mode), type the following prompt:
Configure the Vite dev server proxy in client/vite.config.js so that any request to /bookmarks is forwarded to http://localhost:3001. This lets the React app call the Express API without CORS issues during development.
- Approve the change Copilot makes to vite.config.js.
Why a proxy instead of direct requests?
During development, the React app runs on port 5173 and the Express API runs on port 3001. Browsers block cross-origin requests by default (CORS).
The Vite proxy makes the browser think both are on the same origin. Your React code fetches /bookmarks without specifying a full URL, and Vite forwards it to http://localhost:3001/bookmarks behind the scenes.
Now start both servers and test the full application.
- Open a terminal and start the backend by running:
cd server
npm start
- Open a second terminal (click the + icon in the VS Code terminal panel) and start the frontend by running:
cd client
npm run dev
- Open http://localhost:5173 in your browser. You should see the Bookmark Manager heading and an empty add-bookmark form.
- Add a test bookmark by filling in a URL (like https://nextwork.org), a title, and a tag, then click Add Bookmark.
- Verify the bookmark appears in the list below the form. Click a tag in the sidebar to confirm filtering works.
Bookmarks not showing up?
Check your first terminal to confirm the backend is still running on port 3001. If you see a network error in the browser console, the proxy may not be configured correctly.
Open client/vite.config.js and confirm it has a server.proxy entry pointing /bookmarks to http://localhost:3001.
Help me debug my Vite proxy
- Once everything works, stop both servers with Ctrl+C in each terminal.
- Commit your frontend code by running these commands from the project root:
cd ..
git add .
git commit -m "Add React frontend with bookmark UI components"
✔️ Awesome, I've got everything!
Great. Double check you've saved all files in the client/ directory before moving on.
ⓧ I'd like to double check the full code
Here is the expected code for each file in your client/ directory. If Copilot generated something different, you can replace your code with these versions.
client/vite.config.js:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/bookmarks': 'http://localhost:3001'
}
}
})
client/src/App.jsx:
import { useState, useEffect } from 'react'
import BookmarkList from './components/BookmarkList'
import AddBookmarkForm from './components/AddBookmarkForm'
import TagFilter from './components/TagFilter'
import './App.css'
function App() {
const [bookmarks, setBookmarks] = useState([])
const [selectedTag, setSelectedTag] = useState('')
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
async function fetchBookmarks() {
try {
setLoading(true)
const response = await fetch('/bookmarks')
if (!response.ok) throw new Error('Failed to fetch bookmarks')
const data = await response.json()
setBookmarks(data)
setError(null)
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
useEffect(() => {
fetchBookmarks()
}, [])
async function handleAddBookmark(bookmark) {
try {
const response = await fetch('/bookmarks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(bookmark)
})
if (!response.ok) throw new Error('Failed to add bookmark')
await fetchBookmarks()
} catch (err) {
setError(err.message)
}
}
async function handleDeleteBookmark(id) {
try {
const response = await fetch(`/bookmarks/${id}`, { method: 'DELETE' })
if (!response.ok) throw new Error('Failed to delete bookmark')
await fetchBookmarks()
} catch (err) {
setError(err.message)
}
}
const allTags = [...new Set(bookmarks.flatMap(b => b.tags))]
const filteredBookmarks = selectedTag
? bookmarks.filter(b => b.tags.includes(selectedTag))
: bookmarks
return (
<div className="app">
<h1>Bookmark Manager</h1>
{error && <p className="error">{error}</p>}
<AddBookmarkForm onAdd={handleAddBookmark} />
<div className="content">
<TagFilter
tags={allTags}
selectedTag={selectedTag}
onSelectTag={setSelectedTag}
/>
{loading ? (
<p>Loading bookmarks...</p>
) : (
<BookmarkList
bookmarks={filteredBookmarks}
onDelete={handleDeleteBookmark}
/>
)}
</div>
</div>
)
}
export default App
client/src/components/BookmarkList.jsx:
function BookmarkList({ bookmarks, onDelete }) {
if (bookmarks.length === 0) {
return <p className="empty">No bookmarks yet. Add one above!</p>
}
return (
<ul className="bookmark-list">
{bookmarks.map(bookmark => (
<li key={bookmark.id} className="bookmark-item">
<div className="bookmark-info">
<a href={bookmark.url} target="_blank" rel="noopener noreferrer">
{bookmark.title}
</a>
<span className="bookmark-url">{bookmark.url}</span>
<div className="bookmark-tags">
{bookmark.tags.map(tag => (
<span key={tag} className="tag">{tag}</span>
))}
</div>
</div>
<button onClick={() => onDelete(bookmark.id)} className="delete-btn">
Delete
</button>
</li>
))}
</ul>
)
}
export default BookmarkList
client/src/components/AddBookmarkForm.jsx:
import { useState } from 'react'
function AddBookmarkForm({ onAdd }) {
const [url, setUrl] = useState('')
const [title, setTitle] = useState('')
const [tags, setTags] = useState('')
function handleSubmit(e) {
e.preventDefault()
if (!url || !title) return
onAdd({
url,
title,
tags: tags.split(',').map(t => t.trim()).filter(Boolean)
})
setUrl('')
setTitle('')
setTags('')
}
return (
<form onSubmit={handleSubmit} className="add-form">
<input
type="url"
placeholder="URL"
value={url}
onChange={e => setUrl(e.target.value)}
required
/>
<input
type="text"
placeholder="Title"
value={title}
onChange={e => setTitle(e.target.value)}
required
/>
<input
type="text"
placeholder="Tags (comma separated)"
value={tags}
onChange={e => setTags(e.target.value)}
/>
<button type="submit">Add Bookmark</button>
</form>
)
}
export default AddBookmarkForm
client/src/components/TagFilter.jsx:
function TagFilter({ tags, selectedTag, onSelectTag }) {
return (
<div className="tag-filter">
<h3>Filter by Tag</h3>
<button
className={selectedTag === '' ? 'active' : ''}
onClick={() => onSelectTag('')}
>
All
</button>
{tags.map(tag => (
<button
key={tag}
className={selectedTag === tag ? 'active' : ''}
onClick={() => onSelectTag(tag)}
>
{tag}
</button>
))}
</div>
)
}
export default TagFilter
client/src/App.css:
.app {
max-width: 900px;
margin: 0 auto;
padding: 2rem;
font-family: system-ui, -apple-system, sans-serif;
}
.error {
color: #dc3545;
background: #f8d7da;
padding: 0.75rem;
border-radius: 4px;
}
.add-form {
display: flex;
gap: 0.5rem;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.add-form input {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
flex: 1;
min-width: 150px;
}
.add-form button {
padding: 0.5rem 1rem;
background: #0969da;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.content {
display: flex;
gap: 2rem;
}
.tag-filter {
min-width: 150px;
}
.tag-filter button {
display: block;
width: 100%;
padding: 0.5rem;
margin-bottom: 0.25rem;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
cursor: pointer;
text-align: left;
}
.tag-filter button.active {
background: #0969da;
color: white;
border-color: #0969da;
}
.bookmark-list {
list-style: none;
padding: 0;
flex: 1;
}
.bookmark-item {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 0.5rem;
}
.bookmark-info a {
font-weight: 600;
color: #0969da;
text-decoration: none;
}
.bookmark-url {
display: block;
font-size: 0.85rem;
color: #666;
margin-top: 0.25rem;
}
.bookmark-tags {
margin-top: 0.5rem;
}
.tag {
display: inline-block;
padding: 0.15rem 0.5rem;
background: #e8f0fe;
color: #0969da;
border-radius: 12px;
font-size: 0.75rem;
margin-right: 0.25rem;
}
.delete-btn {
padding: 0.25rem 0.75rem;
background: #dc3545;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.empty {
color: #666;
font-style: italic;
}
Your frontend is live and talking to the backend. Next up, you'll use Plan Mode to add error handling and polish the app's resilience.
Polish with Plan Mode and Context Management
Your Bookmark Manager is functional. You can add, view, delete, and filter bookmarks by tag.
Functional code is a great start, but production-ready code needs proper error handling throughout. In this step, you will use Plan Mode to design an error handling strategy before implementation, then hand that plan to Agent Mode to execute.
In this step, get ready to:
- Use Plan Mode to design error handling improvements across your full-stack app.
- Apply context management best practices to focus Copilot's suggestions.
- Test the full application and commit the polished code.
Use Plan Mode to add error handling
Plan Mode creates a structured implementation plan without immediately changing your code. This gives you a chance to review the strategy before any files are touched.
What is Plan Mode?
Copilot Chat has three modes: Ask, Plan, and Agent. Plan Mode sits between them. It reasons through an implementation approach and presents a step-by-step plan, but does not create or modify files until you approve.
Professionals use Plan Mode to think through cross-cutting concerns (like error handling) that touch multiple files at once. You see the full scope of changes before committing to them.
- Return to the Copilot Chat panel in VS Code.
- At the top of the chat panel, switch the mode dropdown from Agent to Plan.
- Type the following prompt into the chat:
Create a plan for adding comprehensive error handling across the entire app. The backend should validate inputs (return 400 for missing url/title, 404 for missing bookmarks). The frontend should display error messages to the user and show loading indicators while data is being fetched.
What does this prompt do?
This prompt asks Copilot to think holistically about error handling across both the Express API and the React frontend. By specifying the exact behavior you want (400 status codes, 404 responses, visible error messages, loading states), you give Copilot clear acceptance criteria to plan against.
- Review the structured plan Copilot generates. It will outline which files need changes and what each change accomplishes.
- Approve the plan by clicking the approval button or confirming in the chat.
- Switch the mode dropdown back to Agent and ask Copilot to implement the plan it just created.
Copilot will review your existing code and either confirm the error handling is already in place from your earlier Agent Mode work, or make minor refinements. Let it iterate until it is satisfied.
Why hand off from Plan to Agent?
This Explore > Plan > Implement > Review cycle is the professional workflow for AI-assisted development. Plan Mode ensures you understand the scope of changes before Agent Mode executes them. It prevents Agent from going down a path you did not intend.
Plan Mode not appearing?
Make sure you are using the Copilot Chat panel (not inline suggestions). The mode dropdown is at the top of the chat panel, next to the model selector.
If you only see Ask and Agent, try updating your VS Code and the Copilot extension to the latest version.
Why can't I find Plan Mode in my Copilot panel?
Use context management best practices
The quality of Copilot's suggestions depends heavily on what files you have open. Open files act as context that Copilot reads to understand your intent.
- Close all open editor tabs in VS Code by pressing Cmd+K Cmd+W (macOS) or Ctrl+K Ctrl+W (Windows).
- Open only the files relevant to your current task. For example, if you are working on the API routes, open only server/routes/bookmarks.js and server/index.js.
- Close the files when you switch to frontend work, then open client/src/App.jsx and the relevant component files instead.
Why does closing files matter?
Copilot reads your open tabs to understand what you are working on. If you have 15 unrelated files open, Copilot's suggestions become diluted. Keeping only relevant files open focuses its context on your current task.
- Test the @workspace keyword by switching Copilot Chat to Ask mode and typing:
@workspace where is the bookmark validation logic?
What does @workspace do?
The @workspace keyword tells Copilot to search across your entire codebase, not just the open files. Use it when you need to find something but are not sure which file it lives in.
This is different from Agent mode. @workspace in Ask mode answers questions. Agent mode takes actions.
Copilot should point you to server/routes/bookmarks.js where the POST route checks for missing url and title fields.
Final testing and commit
Before committing, you need to confirm that every feature works end-to-end.
- Make sure both servers are running. If they are not, open a terminal in the server/ directory and run npm start, then open a second terminal in the client/ directory and run npm run dev.
- Open your browser to the Vite dev server URL (shown in the terminal output).
- Add a bookmark by filling in a URL, title, and one or more comma-separated tags, then click Add Bookmark.
- Verify the bookmark appears in the list with its tags displayed.
- Click a tag button in the sidebar to confirm filtering works.
- Click Delete on a bookmark and confirm it disappears from the list.
- Try submitting the form with an empty URL or title to confirm the browser prevents it (the required attribute blocks submission).
Test the error state too
Stop the backend server, then refresh the frontend. You should see a red error message appear because the fetch request fails. This confirms your error handling displays correctly.
- Once everything works, commit the polished code by running these commands in your project root:
git add -A
git commit -m "Add error handling and polish application"
Frontend showing a blank page?
Make sure the backend server is running on port 3001 before starting the Vite dev server. The Vite proxy forwards /bookmarks requests to the backend, and if the backend is not running, the proxy connection fails silently.
Also check that you are opening the Vite URL (not localhost:3001) in your browser.
Help me debug why my Bookmark Manager frontend shows a blank page.
Your full-stack Bookmark Manager is polished and committed. Next up, you have an optional Secret Mission to add real-time search functionality.
Secret mission
Add Real-Time Search
Your Bookmark Manager can filter by tags, but what about searching by title or URL? In this mission, you'll add a debounced search bar that filters bookmarks in real-time as you type.
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 server/, client/, and .github/ directories).
- Node.js packages installed in server/node_modules and client/node_modules.
- Local Git repository inside the bookmark-manager folder.
Keep everything running
No action needed. Choose this if you are still actively building on your Bookmark Manager or experimenting with Copilot Agent Mode.
- Your Express server and Vite dev server continue running in the terminal until you close VS Code or press Ctrl+C.
- No cloud resources or subscriptions are active. Everything is local.
- You can restart the servers at any time by running npm start in the server/ directory and npm run dev in the client/ directory.
Pause - I'll come back to this later
Shut down running processes to free up memory. Your project files stay on disk so you can resume later.
- In each terminal where a server is running, press Ctrl+C to stop the process.
- Close VS Code when you are done.
- To resume later, open the bookmark-manager folder in VS Code and start the servers again.
Delete - I don't want to use this again
Remove all project resources and start fresh. This deletes everything you built in this project.
- Stop any running servers by pressing Ctrl+C in each terminal.
- Close VS Code.
- Delete the entire project folder by running the following command in your terminal.
On macOS/Linux:
cd ~/Desktop
rm -rf bookmark-manager
On Windows (PowerShell):
cd ~\Desktop
Remove-Item -Recurse -Force bookmark-manager
- This removes your server/, client/, .github/ directories, all node_modules, and the local Git history.
- Your GitHub Copilot Free activation remains on your GitHub account. No action needed there unless you want to disable it in your GitHub settings.
Nice Work!
Nice Work!
You just built a full-stack Bookmark Manager application using GitHub Copilot Agent Mode as your primary development tool. That is a real, working app with a REST API, a modern frontend, and a professional AI-assisted workflow you can repeat on any future project.
Here's what you accomplished:
- Built a working full-stack app with an Express.js REST API and a React frontend, connected through a Vite dev server proxy.
- Created a .github/copilot-instructions.md file that shapes how Copilot generates code across your entire project.
- Used the professional Agent Mode to Plan Mode to Ask Mode workflow to scaffold features, design error handling, and manage context for higher-quality suggestions.
- Secret Mission: Added real-time search with debouncing using Agent Mode, filtering bookmarks by title and URL as the user types.
Ready to quiz yourself?