This repo contains my code from following the Udemy course Node.js, Express, MongoDB & More: The Complete Bootcamp.
Since the course is now outdated (several tools and APIs have changed), I made adjustments and workarounds to keep the project running in 2025.
You can see the list of changes and extra features I implemented below — this may be useful for fellow students or anyone revisiting the course.
Note
For the original unmodified source code, see the instructor’s repo: complete-node-bootcamp.
If you install the old version used in the course ([email protected]), you’ll see a deprecation warning when connecting to the DB:
(node:15396) [DEP0170] DeprecationWarning: The URL <DATABASE URL> is invalid. Future versions of Node.js will throw an error.
You have two options:
- Upgrade to a newer version of Mongoose.
- Ignore the warning - as of 2025, everything still works despite of it.
I decided to stick with the course’s older version just in case there were breaking changes, but in my experience with newer Mongoose versions (^7) everything (schemas, methods, etc.) still works the same. The only noticeable difference is in the connection options:
//before (v5)
mongoose
.connect(DB, {
useNewUrlParser: true,
useCreateIndex: true,
useFindAndModify: false,
useUnifiedTopology: true, //hide extra warnings
})
.then(() => console.log("DB connection successful!"));
//after (v7)
mongoose
.connect(DB)
.then(() => console.log("DB connection successful!"))
.catch((err) => console.error(err));
In v7 options like useNewUrlParser
, useCreateIndex
and useFindAndModify
no longer exist (they're used by default). useUnifiedTopology
was only needed to suppress old warnings.
While ndb
isn't technically outdated, I found VS Code's built-in debugger more user-friendly and sufficient for this course.
I created a .vscode/launch.json
file following the steps described here: ndb vs Visual Studio debugger, after which VS Code's debugger started working.
Note
Unlike ndb
, VS Code doesn't have a single-step button. Instead there are:
- Step Over - to move forward line by line without entering functions.
- Step Into - to dive into a function call.
- Step Out - to skip the current function and return to the caller.
The structure of Mongoose errors has changed since the course was recorded, which breaks the handleDuplicateFieldsDB
function.
The old err.errmsg
field no longer exists, instead we can get the duplicate keys from err.keyValue
:
//before
const value = err.errmsg.match(/(["'])(\\?.)*?\1/)[0];
//after
const value = Object.values(err.keyValue)
.map((val) => `"${val}"`)
.join(", ");
The course doesn’t cover this error in depth (likely because compound indexes aren’t used until later) - in this project, it can be triggered when creating a tour with a name that already exists, signing up a user with a taken email, or when a user tries to review a tour twice.
To make the error messages more informative for compound indexes ({ tour: 1, user: 1 }, { unique: true }
) I customized the error further:
const collectionMatch = err.message.match(/collection:\s([^.]+)\.(\w+)/);
const collectionName = collectionMatch ? collectionMatch[2] : null;
let message = "";
if (err.keyPattern.tour && err.keyPattern.user) {
if (collectionName === "bookings") {
message = `User ${err.keyValue.user} has already booked tour ${err.keyValue.tour}.`;
} else if (collectionName === "reviews") {
message = `User ${err.keyValue.user} has already left a review on tour ${err.keyValue.tour}.`;
} else {
//fallback (unknown collection)
message = `Duplicate entry for user ${err.keyValue.user} and tour ${err.keyValue.tour} in collection "${collectionName}".`;
}
} else {
const value = Object.values(err.keyValue)
.map((val) => `"${val}"`)
.join(", ");
message = `Duplicate field value: ${value}. Please use another value!`;
}
The default test1234
password wouldn't work for me once I used the import script - it seems that the user passwords in users.json
are already encrypted so letting the model's pre-save middleware encrypt them gets them double encrypted.
I followed the following steps to fix the issue: How to correct POST 401 (Unathorized) Error after Entering Password test1234
As of 2025 Mapbox asks for a credit card to register an account.
I used Leaflet instead using the following setup: Leaflet instead of mapbox
You can see my setup in leaflet.js
and tour.pug
.
Like others I found Sendgrid's requirements way too high for a practice project and used Brevo instead.
Instructions to set it up: SOLUTION!! SendGrid not working? Use Brevo(ex SendinBlue) in 3 simple steps.
My Brevo secrets are taken from the SMTP tab and set up the following way:
BREVO_USERNAME=<Login>
BREVO_PASSWORD=<SMTP key value>
BREVO_EMAIL_HOST=<SMTP Server>
BREVO_EMAIL_PORT=<Port>
EMAIL_FROM=<email>
I set a real email as EMAIL_FROM
, haven't tested with a fake one.
Even when using [email protected] the API doesn't work the same as the course anymore.
Stripe expects a different data structure: StripeInvalidRequestError - version: [email protected] SOLUTION.
The payments don't appear on Stripe right away like in the course, and rather wait until you perform a transaction - here's an explanation on the process and the fake cards Stripe supplies for testing: Payments not in Dashboard.
Additionally, as of 2025 the test payments don't appear in Stripe on a page called Payments
but rather Transactions
.
Lastly, www.natours.dev
is no longer online so it's impossible to use it for images. I solved it by using the course's GitHub repo for the images:
//before
images: [`https://www.natours.dev/img/tours/${tour.imageCover}`];
//after
images: [
`https://github.com/jonasschmedtmann/complete-node-bootcamp/blob/master/4-natours/starter/public/img/tours/${tour.imageCover}?raw=true`,
];
Of course this solution can't work for new custom tours, but the original can't work for new tours either (at least until it's changed to the url of the live website).
Heroku discontinued its free hosting tier in November 2022.
I originally experimented with Heroku before that change, but later migrated my deployments to Fly.io (which, as far as I know, no longer offers a truly free tier either as only legacy users have access to a hobby plan).
Since this course focused specifically on Heroku’s web UI rather than deployment workflows in general, I chose to skip this step and not search for alternative deployment options for the course.
As a result, this project was not tested as a live website. That said, I do have other apps deployed - if you're interested, check out my Fly.io deployment crash course & Docker examples.
Just like the previous time we used Stripe, the website changed quite a bit since the course was recorded so now the process is:
- Go to
Developers
(bottom of the screen) ->Webhooks
-> click+Add destination
.
Note
I initially got a "Limited access to Workbench" message when opening the Webhooks tab. I’m not exactly sure why, but opening Stripe from the welcome email fixed it and refreshed the UI - almost like I was originally partially logged in.
-
The fields in the form changed:
- Endpoint URL -> Not shown in this step, but it appears later.
- Version -> now called API version (presumably can select the latest).
- Events to send -> Open checkout and select
checkout.session.completed
.
-
Click
Continue
, then selectWebhook endpoint
and Continue again. -
Now you'll see the webhook specific fields:
- Destination name: whatever you'd like.
- Endpoint URL: this is the same field as in the course ->
https://<deployed app>/webhook-checkout
. - Description: whatever you'd like.
Note that I skipped deploying the app so I haven't tested the webhook, but there don't appear to be any settings in this flow that would prevent the webhook from working.
From the list of suggested challenges in this unit, I implemented the following:
- Users can only review tours they have booked:
- Added middleware
hasBookedTour
inreviewController.js
- Updated
reviewRoutes.js
- Added middleware
- Nested booking routes (
GET
&POST /tours/:id/bookings
):- Added nested route in
tourRoutes.js
- Updated
bookingRoutes.js
andbookingController.js
- Added nested route in
- Hide bookings section if user already booked the tour & prevent duplicate bookings:
- Updated
bookingModel.js
- Added check in
viewsController.js
- Updated
tour.pug
- Updated
- "My Reviews" page (non-React):
- Added
getMyReviews
inviewsController.js
- Added route
/my-reviews
inviewRoutes.js
- Added
reviews.pug
with custom cards
- Added