Build a REST API with Node.js and MongoDB
Build a Bookmark Manager REST API with Express and a cloud MongoDB database.
Introduction
30 Second Summary
Every app you use (Instagram, Spotify, a job board) talks to a backend server to fetch and store data. That server is a REST API.
In this project, you will build a Bookmark Manager API using Node.js, Express, and MongoDB. You will create endpoints that save, retrieve, update, and delete bookmarks, all connected to a real cloud database.
What You'll Build
A REST API built with Node.js and Express that performs full CRUD operations on a MongoDB Atlas cloud database.
By the end of this project, you'll have:
- A Node.js/Express REST API with 5 endpoints for creating, reading, updating, and deleting bookmarks.
- A Mongoose schema that defines and validates the shape of your bookmark data.
- A live MongoDB Atlas cloud database storing real data you created through your API.
- Secret Mission: Add a search endpoint that filters bookmarks by tag or title keyword.
Are there any prerequisites?
You should have basic programming knowledge in any language (C, C++, or Python all work). You also need VS Code installed and a GitHub account ready to go.
No JavaScript experience is required. This project teaches you JavaScript as you build.
Set Up the Project Structure
With Node.js installed and your project folder ready, the next step is setting up the internal structure of your project.
A professional Node.js project uses a package.json file to track metadata and dependencies, plus a clear folder layout that separates concerns. In this step, you will initialize npm, install your core packages, and scaffold the folder structure your API will live in.
In this step, get ready to:
- Initialize npm and generate a package.json file.
- Install Express, Mongoose, and dotenv packages.
- Create the project file and folder structure.
Initialize npm and install dependencies
The package.json file is the heart of any Node.js project. It tracks your project's name, version, and every package your code depends on.
- Generate a package.json file by running this command in your terminal:
npm init -y
What does this command do?
npm init creates a new package.json for your project. The -y flag auto-fills every prompt with default values so you skip the interactive questionnaire.
The generated file has some default values that need updating for this project.
- Click on package.json in the Explorer sidebar to open it.
- Replace the entire contents of the file with the following:
{
"name": "bookmark-api",
"version": "1.0.0",
"description": "A REST API for managing bookmarks",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"keywords": [],
"author": "",
"license": "ISC"
}
What changed from the defaults?
- "description" gives your project a human-readable summary.
- "main": "server.js" tells Node.js which file to run as the entry point.
- "scripts" defines command shortcuts. "start": "node server.js" lets you later run npm start to launch your server.
- Save package.json.
- Install the three packages your project needs by running:
npm install express mongoose dotenv
What are these packages?
- express is a web framework that handles HTTP requests and routing for your API.
- mongoose connects your app to MongoDB and lets you define data models with validation.
- dotenv loads secret values (like database passwords) from a .env file so they stay out of your code.
- Confirm a node_modules folder appeared in your Explorer sidebar.
- Open package.json again. You should see a new "dependencies" section listing express, mongoose, and dotenv.
Seeing an error during install?
- Make sure you saved package.json before running npm install. An unsaved file with invalid JSON will cause errors.
- Check that your terminal is inside the bookmark-api folder. Run pwd (macOS) or cd (Windows) to verify.
Help me fix my npm install error
Create the project file structure
Your API needs a clean separation between the entry point, data models, and route handlers. You will create empty placeholder files now and fill them in over the next steps.
- Create the project files and folders by running these commands:
macOS
touch server.js .env .gitignore
mkdir models routes
touch models/Bookmark.js routes/bookmarks.js
Windows
New-Item -ItemType File -Path server.js, .env, .gitignore
New-Item -ItemType Directory -Path models, routes
New-Item -ItemType File -Path models\Bookmark.js, routes\bookmarks.js
What is this folder structure?
- server.js is the entry point that starts your Express server.
- models/ holds your Mongoose data models (how your data is shaped).
- routes/ holds your API endpoint handlers (what your API responds to).
- .env stores secrets like your database connection string.
- .gitignore tells Git which files to never commit.
- Verify the files were created by running:
ls
You should see server.js, .env, .gitignore, models, routes, node_modules, and package.json in the output.
Now you need to tell Git which files to ignore. The node_modules folder is huge and should never be committed. Your .env file contains secrets that must stay local.
- Open .gitignore in the Explorer sidebar and add the following content:
node_modules
.env
What does .gitignore do?
Any file or folder listed in .gitignore becomes invisible to Git. This keeps sensitive files (like .env) out of your repository and avoids pushing thousands of dependency files in node_modules.
- Save .gitignore.
Don't see some of the files?
- Files starting with a dot (like .env and .gitignore) are hidden by default in some file explorers. In VS Code's Explorer sidebar, they should still appear.
- If ls does not show dotfiles, run ls -a (macOS/Linux) to reveal them.
Help me find my project files
✔️ Awesome, I've got everything!
Great. Double check you have saved both package.json and .gitignore.
ⓧ I'd like to double check the full code
Here is what each file should look like at this point.
{
"name": "bookmark-api",
"version": "1.0.0",
"description": "A REST API for managing bookmarks",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "16.5.0",
"express": "5.1.0",
"mongoose": "8.14.3"
}
}
node_modules
.env
The files server.js, models/Bookmark.js, routes/bookmarks.js, and .env should all exist but be empty. You will fill them in over the next steps.
Your project skeleton is locked in. Next up, you will connect to a real cloud database using MongoDB Atlas.
Connect to MongoDB Atlas
Your project folder is set up and all dependencies are installed. Now your API needs somewhere to store data permanently.
Every REST API needs a database behind it. You'll use MongoDB Atlas, a free cloud database, so your bookmarks persist between server restarts.
In this step, get ready to:
- Create a MongoDB Atlas account and deploy a free cluster.
- Configure the connection string in your environment file.
- Write the database connection code in server.js.
Create a MongoDB Atlas account and free cluster
MongoDB Atlas is a cloud-hosted database service with a free tier that gives you 512 MB of storage. You do not need to install any database software locally.
- Open your browser and go to mongodb.com/cloud/atlas/register.
- Sign up for a free account using your email or Google account.
- After signing in, Atlas prompts you to deploy your first cluster. Select the M0 option (this is the free tier that never expires).
- Choose any cloud provider (AWS, GCP, or Azure) and any available region.
- Click Create to deploy your cluster.
After deployment, Atlas shows a Security Quickstart wizard to configure access.
- In the Security Quickstart, enter a Username for your database user.
- Enter a Password (or copy the suggested secure password). Save these credentials somewhere safe.
- Click Create Database User.
- Under Network Access, click Add My Current IP Address.
- Click Finish and Close.
- Click Go to Overview to return to your cluster dashboard.
Why allow access from anywhere?
For development, you can also whitelist 0.0.0.0/0 under Network Access to allow connections from any IP address. This saves you from updating the whitelist every time your IP changes (like switching Wi-Fi networks).
Never do this for a production database. For this learning project, it is perfectly safe.
Get the connection string and configure your environment
Atlas provides a connection string that your code uses to find and authenticate with your cluster. You will store this in your .env file so it stays out of your source code.
- On the cluster overview page, click the Connect button on your cluster.
- Select Drivers as your connection method.
- Choose Node.js as the driver.
- Copy the connection string shown. It looks like mongodb+srv://<username>:<password>@cluster0.xxxxx.mongodb.net/.
- Open .env in VS Code and add your connection string with the database name appended:
MONGODB_URI=mongodb+srv://your-username:your-password@cluster0.xxxxx.mongodb.net/bookmarks
PORT=3000
What does this code do?
- MONGODB_URI stores the full address of your database, including your credentials and the database name bookmarks.
- PORT sets which port your Express server listens on.
- Replace your-username with the database username you created in the Security Quickstart.
- Replace your-password with the password you set for that user.
- Replace cluster0.xxxxx with the actual cluster address from your copied connection string.
- Save .env.
Why use environment variables?
Your .env file keeps sensitive credentials (like database passwords) separate from your code. Since .env is already in your .gitignore, it will never be pushed to GitHub.
Not sure which part to replace?
Your copied connection string from Atlas already has the cluster address filled in. You only need to replace the <password> placeholder with your actual password and add /bookmarks before the ? (or at the end if there is no ?).
help me figure out what to put in my connection string
Write the database connection in server.js
With the connection string ready, you can write the code that connects your Express server to MongoDB using Mongoose.
- Open server.js in VS Code and add the following code to set up Express and connect to your database:
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
// Load environment variables from .env file
dotenv.config();
// Create the Express app
const app = express();
const PORT = process.env.PORT || 3000;
// Parse incoming JSON request bodies
app.use(express.json());
What does this code do?
- require('express'), require('mongoose'), and require('dotenv') import the three packages you installed earlier.
- dotenv.config() reads your .env file and makes its values available through process.env.
- express() creates your application instance.
- express.json() is middleware that automatically parses JSON data sent to your API in request bodies.
- Below the middleware line, add the database connection and server startup code:
// Connect to MongoDB and start the server
mongoose.connect(process.env.MONGODB_URI)
.then(() => {
console.log('Connected to MongoDB');
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
})
.catch((error) => {
console.error('MongoDB connection error:', error.message);
});
What does this code do?
- mongoose.connect() uses your connection string to reach MongoDB Atlas. It returns a Promise, which is why you chain .then() and .catch().
- Inside .then(), the server only starts listening after the database connection succeeds. This prevents your API from accepting requests before the database is ready.
- .catch() logs the error message if the connection fails, so you can diagnose the problem.
- Save server.js.
- Test the connection by running this command in your integrated terminal:
node server.js
You should see two messages printed in the terminal:
Connected to MongoDB
Server running on port 3000
Seeing a connection error?
- Double-check your .env file for typos in the connection string. The username, password, and cluster address must match exactly what Atlas shows.
- Make sure your IP address is whitelisted in Atlas under Network Access. If you switched networks since creating the cluster, add your new IP or use 0.0.0.0/0.
- Confirm there are no extra spaces or quote marks around the MONGODB_URI value in your .env file.
help me fix my MongoDB connection error
- Press Ctrl+C in the terminal to stop the server for now.
✔️ Awesome, I've got everything!
Great. Double check you have saved both .env and server.js.
ⓧ I'd like to double check the full code
Here is the full .env file:
MONGODB_URI=mongodb+srv://your-username:your-password@cluster0.xxxxx.mongodb.net/bookmarks
PORT=3000
Here is the full server.js file:
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
mongoose.connect(process.env.MONGODB_URI)
.then(() => {
console.log('Connected to MongoDB');
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
})
.catch((error) => {
console.error('MongoDB connection error:', error.message);
});
Your database is connected and your server is ready to accept requests. Next up, you'll define the shape of your bookmark data with a Mongoose model.
Define the Mongoose Model
Your server is connected to MongoDB Atlas and ready to store data. But MongoDB needs to know what shape that data should take.
In this step, you'll define a Mongoose schema for your bookmarks. A schema is a blueprint that describes the fields, types, and rules for each document stored in your database.
In this step, get ready to:
- Define the Bookmark schema with five fields in models/Bookmark.js.
- Verify the model exports without errors.
Create the Bookmark schema
Each bookmark in your API needs a title, a URL, an optional description, tags for categorization, and a timestamp. The schema enforces these rules so that incomplete or malformed data never enters your database.
- In VS Code, open models/Bookmark.js from the Explorer sidebar.
- Add the following code to define the Bookmark schema and export the model:
const mongoose = require('mongoose');
// Define the shape of a bookmark document
const bookmarkSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
url: {
type: String,
required: true
},
description: {
type: String,
default: ''
},
tags: {
type: [String],
default: []
},
createdAt: {
type: Date,
default: Date.now
}
});
// Create and export the Bookmark model
module.exports = mongoose.model('Bookmark', bookmarkSchema);
What does this code do?
This file defines the rules for bookmark documents in your MongoDB database.
- title and url are required. Mongoose will reject any bookmark missing these fields.
- description defaults to an empty string, and tags defaults to an empty array. Both are optional when creating a bookmark.
- createdAt automatically records the timestamp when a bookmark is created.
- mongoose.model('Bookmark', bookmarkSchema) compiles the schema into a model you can use to create, read, update, and delete documents.
- Save models/Bookmark.js.
- Verify the model loads without errors by running this command in your terminal:
node -e "require('./models/Bookmark')"
If the command completes silently with no output, your model is valid. No news is good news here.
Seeing a syntax error?
- Check for missing commas between the field definitions. Each field object (like title: { ... }) needs a comma after its closing brace.
- Make sure you have matching curly braces. The outer mongoose.Schema({ ... }) wraps all five field definitions.
- Confirm the file is saved in the models folder, not in the project root.
Why isn't my Bookmark model loading in Node?
How does a Mongoose schema compare to C structs or Python classes?
If you've written a C struct or Python dataclass, you already understand schemas. They all define the shape of your data ahead of time.
MongoDB itself is flexible and will accept any shape of data. Mongoose adds a layer of discipline on top. When you try to save a bookmark without a title, Mongoose will reject it before it ever reaches the database.
✔️ 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 models/Bookmark.js file for comparison:
const mongoose = require('mongoose');
const bookmarkSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
url: {
type: String,
required: true
},
description: {
type: String,
default: ''
},
tags: {
type: [String],
default: []
},
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Bookmark', bookmarkSchema);
Your data model is locked in. Next up, you'll build the API routes that let you create, read, update, and delete bookmarks using this model.
Build the CRUD API Routes
Your Bookmark model defines the shape of your data. Now you need a way for clients to actually interact with that data over HTTP.
In this step, you will build five REST API endpoints that let any client create, read, update, and delete bookmarks. These five operations (called CRUD) are the foundation of almost every backend application.
In this step, get ready to:
- Write GET and POST routes to read and create bookmarks.
- Add GET by ID, PUT, and DELETE routes for single-bookmark operations.
- Register the routes in your server and test all five endpoints with Thunder Client.
Create the routes file with GET and POST
Every Express app uses a Router to group related endpoints together. You will create a router for bookmarks and define two routes: one to fetch all bookmarks and one to create a new bookmark.
- Open routes/bookmarks.js in VS Code. This file is currently empty.
- Add the following code to set up the router and create your first route (GET all bookmarks):
const express = require('express');
const router = express.Router();
const Bookmark = require('../models/Bookmark');
// GET all bookmarks
router.get('/', async (req, res) => {
try {
const bookmarks = await Bookmark.find().sort({ createdAt: -1 });
res.json(bookmarks);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
What does this code do?
The first three lines import Express, create a Router (a mini-app for handling related routes), and import your Bookmark model from the previous step.
- router.get('/', ...) handles GET requests to the base path. When this router is mounted at /api/bookmarks, this responds to GET /api/bookmarks.
- Bookmark.find().sort({ createdAt: -1 }) fetches all bookmarks from MongoDB, sorted newest first.
- The try/catch block catches database errors and returns a 500 status code if something goes wrong.
You won't see a change yet. The next chunk adds the POST route.
- Below your GET route (after the closing });), add the POST route and the module export:
// POST a new bookmark
router.post('/', async (req, res) => {
const bookmark = new Bookmark({
title: req.body.title,
url: req.body.url,
description: req.body.description,
tags: req.body.tags
});
try {
const savedBookmark = await bookmark.save();
res.status(201).json(savedBookmark);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
module.exports = router;
What does this code do?
- router.post('/', ...) handles POST requests. The client sends a JSON body, and req.body contains that data.
- new Bookmark({...}) creates a new document instance from the request data. Calling .save() writes it to the database.
- Status 201 means "created successfully". Status 400 means the client sent invalid data (like a missing required field).
- module.exports = router exports the router so server.js can import and use it.
- Save routes/bookmarks.js.
- Confirm there are no syntax errors by running this in your terminal:
node -e "require('./routes/bookmarks')"
If no errors appear (the command returns silently), your code is valid.
Seeing a syntax error?
- Check that every opening ( has a matching ) and every { has a matching }. Route handlers have nested brackets that are easy to miscount.
- Make sure the path in the require statement is '../models/Bookmark' with two dots. The routes/ folder is one level deeper than your project root.
- help me debug a syntax error in my Express routes file
Add GET by ID, PUT, and DELETE routes
Your GET all and POST routes handle collections of bookmarks. Now you need routes that operate on a single bookmark, identified by its unique _id from MongoDB.
- In routes/bookmarks.js, find your GET all route's closing });. Add this GET by ID route directly below it (above your POST route):
// GET a single bookmark by ID
router.get('/:id', async (req, res) => {
try {
const bookmark = await Bookmark.findById(req.params.id);
if (!bookmark) {
return res.status(404).json({ message: 'Bookmark not found' });
}
res.json(bookmark);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
What does this code do?
- /:id is a route parameter. When a client requests /api/bookmarks/abc123, Express captures abc123 in req.params.id.
- Bookmark.findById() searches MongoDB for a document with that specific _id.
- If no bookmark is found, the route returns a 404 status ("not found") instead of crashing.
You won't see a change yet. The next route handles updates.
- Below your POST route's closing }); (but above module.exports = router;), add the PUT route:
// PUT (update) a bookmark by ID
router.put('/:id', async (req, res) => {
try {
const updatedBookmark = await Bookmark.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
if (!updatedBookmark) {
return res.status(404).json({ message: 'Bookmark not found' });
}
res.json(updatedBookmark);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
What does this code do?
- Bookmark.findByIdAndUpdate() finds a bookmark by ID and replaces its fields with the new data from req.body.
- { new: true } tells Mongoose to return the updated document (not the old one).
- { runValidators: true } re-checks your schema rules (like required: true) on the updated data.
You won't see a change yet. One more route to go.
- Below the PUT route (still above module.exports = router;), add the DELETE route:
// DELETE a bookmark by ID
router.delete('/:id', async (req, res) => {
try {
const deletedBookmark = await Bookmark.findByIdAndDelete(req.params.id);
if (!deletedBookmark) {
return res.status(404).json({ message: 'Bookmark not found' });
}
res.json({ message: 'Bookmark deleted' });
} catch (error) {
res.status(500).json({ message: error.message });
}
});
What does this code do?
- Bookmark.findByIdAndDelete() removes the document from MongoDB permanently.
- The route returns a confirmation message on success. If the ID does not exist, it returns a 404.
- Save routes/bookmarks.js.
- Verify all five routes load without errors by running:
node -e "require('./routes/bookmarks')"
No output means success. Your routes file is complete.
Seeing a Cannot find module error?
- Make sure models/Bookmark.js exists and the require path is '../models/Bookmark' (two dots because you are inside the routes/ folder).
- Confirm you are running the command from the bookmark-api folder, not from inside routes/.
- help me fix a Cannot find module error in my Express router
✔️ Awesome, I've got everything!
Great. Double check you have saved routes/bookmarks.js before moving on.
ⓧ I'd like to double check the full code
const express = require('express');
const router = express.Router();
const Bookmark = require('../models/Bookmark');
// GET all bookmarks
router.get('/', async (req, res) => {
try {
const bookmarks = await Bookmark.find().sort({ createdAt: -1 });
res.json(bookmarks);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
// GET a single bookmark by ID
router.get('/:id', async (req, res) => {
try {
const bookmark = await Bookmark.findById(req.params.id);
if (!bookmark) {
return res.status(404).json({ message: 'Bookmark not found' });
}
res.json(bookmark);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
// POST a new bookmark
router.post('/', async (req, res) => {
const bookmark = new Bookmark({
title: req.body.title,
url: req.body.url,
description: req.body.description,
tags: req.body.tags
});
try {
const savedBookmark = await bookmark.save();
res.status(201).json(savedBookmark);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
// PUT (update) a bookmark by ID
router.put('/:id', async (req, res) => {
try {
const updatedBookmark = await Bookmark.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
if (!updatedBookmark) {
return res.status(404).json({ message: 'Bookmark not found' });
}
res.json(updatedBookmark);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
// DELETE a bookmark by ID
router.delete('/:id', async (req, res) => {
try {
const deletedBookmark = await Bookmark.findByIdAndDelete(req.params.id);
if (!deletedBookmark) {
return res.status(404).json({ message: 'Bookmark not found' });
}
res.json({ message: 'Bookmark deleted' });
} catch (error) {
res.status(500).json({ message: error.message });
}
});
module.exports = router;
Register routes in server.js and test
Your routes exist but your server does not know about them yet. You need to import the router and mount it at a URL path so Express knows which requests to forward to your bookmarks routes.
- In server.js, find the line that says app.use(express.json());.
- Add these two lines directly below it:
const bookmarkRoutes = require('./routes/bookmarks');
app.use('/api/bookmarks', bookmarkRoutes);
What does this code do?
- require('./routes/bookmarks') imports the router you just built.
- app.use('/api/bookmarks', bookmarkRoutes) tells Express: any request starting with /api/bookmarks should be handled by this router. Your router's / becomes /api/bookmarks, and /:id becomes /api/bookmarks/:id.
- Save server.js.
- If your server is still running from the previous step, press Ctrl+C in the terminal to stop it.
- Restart your server by running:
node server.js
You should see "Connected to MongoDB" and "Server running on port 3000" in your terminal.
Seeing an address already in use error?
- Your previous server process is still running. Press Ctrl+C in the terminal to stop it, then run node server.js again.
- help me fix an address already in use error when starting my Express server
✔️ Awesome, I've got everything!
Great. Double check you have saved server.js before moving on to testing.
ⓧ I'd like to double check the full code
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
const bookmarkRoutes = require('./routes/bookmarks');
app.use('/api/bookmarks', bookmarkRoutes);
mongoose.connect(process.env.MONGODB_URI)
.then(() => {
console.log('Connected to MongoDB');
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
})
.catch((error) => {
console.error('MongoDB connection error:', error.message);
});
Now you will test each endpoint with Thunder Client to confirm everything works.
- Click the Thunder Client icon in the Activity Bar (left sidebar) in VS Code.
- Click New Request.
- Set the method to POST from the dropdown.
- Enter this URL:
http://localhost:3000/api/bookmarks
- Click the Body tab below the URL bar.
- Select JSON from the content type dropdown.
- Paste this JSON into the body area:
{
"title": "NextWork",
"url": "https://nextwork.org",
"description": "Cloud and DevOps projects",
"tags": ["learning", "cloud"]
}
- Click Send.
You should see a 201 response with the bookmark data, including a generated _id field.
What should I see?
The response panel shows a JSON object with all the fields you sent, plus _id (a unique MongoDB identifier), createdAt (the timestamp), and __v (a Mongoose version key). The status code in the top right says 201 Created.
- Copy the _id value from the response. You will need it for the next tests.
- Change the method to GET and click Send (keep the same URL).
You should see an array containing the bookmark you just created.
- Append your copied _id to the URL (e.g. http://localhost:3000/api/bookmarks/YOUR_ID_HERE) and click Send with GET.
You should see just that single bookmark returned.
- Change the method to PUT (keep the ID in the URL).
- In the Body tab, paste this updated JSON:
{
"title": "NextWork - Updated",
"description": "Hands-on cloud projects"
}
- Click Send.
You should see the bookmark returned with the updated title and description.
- Change the method to DELETE (keep the ID in the URL).
- Click Send.
You should see the message "Bookmark deleted" in the response.
Verify in MongoDB Atlas
To see your data in the cloud, go to your MongoDB Atlas dashboard. Click Browse Collections on your cluster. You will see a bookmarks database with a bookmarks collection.
Any bookmarks you created (and did not delete) appear here as documents.
Getting a 404 or connection error in Thunder Client?
- Make sure your server is still running (check the terminal for "Server running on port 3000").
- Confirm the URL is exactly http://localhost:3000/api/bookmarks with no trailing spaces or typos.
- If POST returns a 400 error, check that you selected JSON in the Body tab (not Text or Form).
- help me troubleshoot my Express API returning errors in Thunder Client
All five CRUD endpoints are working. Next up, you will extend your API with a search feature in the Secret Mission.
Secret mission
Add Search by Tag or Keyword
Your API can create, read, update, and delete bookmarks. But what if you have hundreds of bookmarks and need to find the ones tagged "javascript" or containing "React" in the title? Add a search endpoint that filters bookmarks using MongoDB query operators.
Clean Up Your Resources
Clean Up Your Resources
This project uses MongoDB Atlas's free M0 tier, which costs nothing to keep running. There are no ongoing charges. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- MongoDB Atlas M0 cluster (free tier, no charges).
- Local bookmark-api project folder on your Desktop.
- Thunder Client VS Code extension.
Keep everything running
No action needed. Choose this if you're still actively building or plan to extend the API soon.
- Your Atlas M0 cluster stays free forever with 512 MB of storage. No charges will occur.
- Atlas automatically pauses free clusters after 30 days of inactivity. You can resume it anytime by visiting your Atlas dashboard.
- Your local code stays on your machine in the bookmark-api folder.
- Thunder Client remains installed in VS Code for testing future APIs.
Pause - I'll come back to this later
Stop the running server to free up your terminal. Atlas handles itself automatically.
- If your server is still running in the VS Code terminal, press Ctrl+C to stop it.
- Your MongoDB Atlas free cluster automatically pauses after 30 days of inactivity. No action needed on your part.
- Your local files remain untouched in the bookmark-api folder.
- To resume later, open the bookmark-api folder in VS Code and run node server.js again.
Delete - I don't want to use this again
Remove all project resources and start fresh. This deletes your data permanently.
- Terminate your MongoDB Atlas cluster:
- Go to your MongoDB Atlas dashboard and click Clusters in the left sidebar under the Database heading.
- Click the ... (ellipsis) button next to your cluster.
- Click Terminate from the dropdown menu.
- Type your cluster name to confirm, then click Terminate.
- Delete the local project folder. On macOS or Linux, run:
cd ~/Desktop
rm -rf bookmark-api
- On Windows, run:
cd ~\Desktop
rmdir /s /q bookmark-api
- Uninstall Thunder Client from VS Code by opening the Extensions sidebar, searching for Thunder Client, and clicking Uninstall.
Nice Work!
Nice Work!
You just built a fully functional REST API from scratch using Node.js, Express, and MongoDB. This is the same backend architecture powering thousands of production applications.
You've learned how to:
- Built a REST API with Express that handles full CRUD operations across 5 endpoints.
- Connected to a cloud MongoDB Atlas database using Mongoose and defined structured data models with schemas.
- Tested and verified every API endpoint using Thunder Client directly inside VS Code.
- Secret Mission: Added a search endpoint with MongoDB query operators and case-insensitive regex filtering.
Ready to quiz yourself?