In the previous lesson you installed dotenv and npm wrote ^17.2.1 in the manifest, not 17.2.1. That caret is a decision with enormous consequences: it means your project is not asking for a version, it is asking for a range, and the specific version that ends up installed depends on what dotenv's author has published by the day someone runs npm install.
That is either a time bomb or an extraordinary convenience, depending on how you understand it. Without package-lock.json, two people on the same team can install the same package.json on different days and end up with different dependency trees. With it, the install is reproducible down to the last byte.
This lesson has two halves that explain each other: the versioning system that promises compatibility and the file that makes that promise unnecessary.
Contents
- SemVer:
MAJOR.MINOR.PATCHand the implicit contract - Pre-release versions and their precedence order
- The range table, with the row almost nobody knows
- What each range installs today and six months from now
package-lock.json: what it contains exactlynpm civersusnpm install- Lock conflicts in git
npm dedupeand--save-exact- The minor update that breaks things
- From
0.1.0to1.0.0: versioning Escena Viva
- SemVer:
MAJOR.MINOR.PATCH and the implicit contract
MAJOR.MINOR.PATCH and the implicit contractSemantic versioning (semver.org) is a convention that turns three numbers into a verifiable promise:
17 . 2 . 1
│ │ │
│ │ └── PATCH: compatible fixes
│ └────────── MINOR: new functionality, compatible
└────────────────── MAJOR: changes that break compatibilityThe rules for when each number goes up:
| Type of change | Bumps | Example |
|---|---|---|
| Fixing a bug without changing the API | PATCH | 17.2.1 → 17.2.2 |
| Adding a new function or option | MINOR | 17.2.1 → 17.3.0 |
| Marking something deprecated (without removing it) | MINOR | 17.2.1 → 17.3.0 |
| Removing or renaming something public | MAJOR | 17.2.1 → 18.0.0 |
| Changing the behavior of an existing function | MAJOR | 17.2.1 → 18.0.0 |
| Raising the minimum required Node version | MAJOR | 17.2.1 → 18.0.0 |
| Changing comments, the README or internal tests | Nothing | Not published |
When a number goes up, everything to its right resets to zero: from 17.2.1, a minor bump gives 17.3.0, and a major bump gives 18.0.0.
The implicit contract is the part people forget. Publishing 17.3.0 instead of 18.0.0 is not an aesthetic preference: it is a statement to thousands of projects that they can upgrade without reviewing their code. And since 90% of those projects have ^17.2.1 written down, that minor version will enter their installs automatically. If you lied, their builds break tomorrow without them having changed a line.
That is why semver is, before it is a technical rule, a matter of responsibility. And it is also why you cannot fully trust it: it is a convention, not something the registry verifies. What does get verified is the lockfile, and that is why the two halves of this lesson belong together.
- Pre-release versions and their precedence order
Before a stable version, test versions are published with a hyphen:
The precedence rules, as defined by the specification: a version with a pre-release tag is always lower than the same version without one (1.0.0-rc.1 < 1.0.0, because it is a rehearsal for that version, not a later one); identifiers are compared left to right; numeric ones as numbers and alphanumeric ones alphabetically; a number is always lower than text (1.0.0-1 < 1.0.0-alpha); and when everything above ties, whoever has more identifiers wins (1.0.0-alpha < 1.0.0-alpha.1).
A complete ordered series:
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta
< 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0Notice beta.2 < beta.11: because the identifier is numeric it is compared as a number, not as text. If it were compared as text, 11 would come before 2.
The most important part day to day: pre-release versions never enter a normal range. If your manifest says ^1.0.0 and 2.0.0-beta.1 is published, npm will not install it. To receive them you have to ask explicitly (npm install package@beta) or write a range that includes them. It is a deliberate protection, and it is the right one.
- The range table, with the row almost nobody knows
This is the table to know by heart. Suppose the latest published version is 1.9.2 and that 2.0.0 and 1.2.9 also exist.
Range in package.json |
Name | Allows | Installs today |
|---|---|---|---|
1.2.3 |
Exact | Only 1.2.3 |
1.2.3 |
~1.2.3 |
Tilde | Patches: >=1.2.3 <1.3.0 |
1.2.9 |
^1.2.3 |
Caret | Minors and patches: >=1.2.3 <2.0.0 |
1.9.2 |
1.2.x / 1.2 |
Partial wildcard | Same as ~1.2.0 |
1.2.9 |
1.x / 1 |
Partial wildcard | Same as ^1.0.0 |
1.9.2 |
>=1.2.3 <2 |
Explicit range | What it says | 1.9.2 |
1.2.3 - 1.5.0 |
Hyphen | Both ends included | 1.5.0 |
* or "" |
Anything | Any version | 2.0.0 |
latest |
Tag | Whatever is tagged latest |
2.0.0 |
The short way to remember the two important ones: ~ lets the last number rise; ^ lets the last non-zero-from-the-left one rise.
The row almost nobody knows: ^0.x.y
Here is the detail that produces unpleasant surprises. The caret is defined as "do not change the first non-zero number", and that gives different behavior when the major is 0:
| Range | Equivalent to | Comment |
|---|---|---|
^1.2.3 |
>=1.2.3 <2.0.0 |
The usual one |
^0.2.3 |
>=0.2.3 <0.3.0 |
Patches only: 0.2 acts as if it were the major |
^0.0.3 |
>=0.0.3 <0.0.4 |
That version only: equivalent to pinning it |
The logic is sound: in 0.x the library declares that its API is still moving, so semver treats every minor version as potentially incompatible. The practical consequence is surprising though: an npm update moves nothing in a 0.x package apart from patches. If you expected to go from 0.2.9 to 0.4.0 automatically, it will not happen; you have to change the range by hand.
And the corollary for when you publish: while you are on 0.x you promise nothing, and whoever uses you knows it. That is precisely why Escena Viva started at 0.1.0 and not at 1.0.0.
- What each range installs today and six months from now
The thought experiment that clarifies the problem. Today, with [email protected] as the latest published version:
| Range | Installs today |
|---|---|
17.2.1 |
17.2.1 |
~17.2.1 |
17.2.1 |
^17.2.1 |
17.2.1 |
All three agree. Now fast-forward six months: 17.2.5, 17.6.0 and 18.0.0 have been published. Someone clones the repository, runs npm install without having touched package.json and gets:
| Range | Installs six months later | Did your code change? |
|---|---|---|
17.2.1 |
17.2.1 |
No |
~17.2.1 |
17.2.5 |
No |
^17.2.1 |
17.6.0 |
No |
* |
18.0.0 |
No |
Three people with the same repository and three different trees. That is exactly the problem the lockfile solves.
Compare the recommended range according to the project's role:
| Situation | Recommended range | Reason |
|---|---|---|
| Application with a lock (Escena Viva) | ^ |
The lock already pins the real version; the range only states the update policy |
| Published library | Broad ^ |
Narrow ranges cause duplicates in the projects that use you |
| Dependency with a history of breaking | ~ or exact |
Less room for surprises |
| Any project | Never * |
You give up every guarantee in exchange for nothing |
package-lock.json: what it contains exactly
package-lock.json: what it contains exactlyIt is generated on its own as soon as there is any dependency. Here is a real fragment, trimmed:
{
"name": "escena-viva",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "escena-viva",
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"dotenv": "^17.2.1"
},
"devDependencies": {
"prettier": "^3.6.2"
},
"engines": {
"node": ">=24.5.0 <25"
}
},
"node_modules/dotenv": {
"version": "17.2.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz",
"integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78hjB1PYYow==",
"engines": { "node": ">=12" },
"funding": { "url": "https://dotenvx.com" }
},
"node_modules/prettier": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfQqFsuHLoDrIhcVYWyO0nAvqEhIkKMYQIsy0mRnNlqUXWkuVjK4X8oEeWmnCLYUEQA==",
"dev": true,
"bin": { "prettier": "bin/prettier.cjs" },
"engines": { "node": ">=14" }
}
}
}Field by field:
| Field | What it is |
|---|---|
lockfileVersion: 3 |
The file format. Version 3 is npm 7 onwards |
packages[""] |
The project itself: a copy of its declared ranges |
version |
The exact resolved version. No ranges here |
resolved |
The exact URL of the downloaded tarball |
integrity |
SHA-512 hash of the content, in SRI format |
dev: true |
Marks it as needed only in development (enables --omit=dev) |
bin |
Executables to be linked into node_modules/.bin |
engines |
The package's Node requirements, copied so they can be validated |
The integrity field is what makes this security and not just convenience. On every install npm downloads the tarball, computes its SHA-512 and compares it with the recorded one. If they do not match —because someone tampered with the registry, because a mirror served something else, because a cache got corrupted— the install fails. That is cryptographic integrity across your whole dependency tree, for free.
Hence the rule that is not up for negotiation:
package-lock.jsonDOES go into version control. Always.
The reasons: reproducibility (your laptop, your teammate's, CI and the server all install the same thing, today and a year from now); security, because the hashes travel with the repository; debugging, since git log package-lock.json tells you which version changed and when, which is usually the answer to "this worked last week"; and auditing, because npm audit analyzes the lock's exact tree and not an approximation.
The only real exception is a published library: its lock does not affect whoever installs it, because every project resolves its own tree. Even so, many libraries commit it so their own tests are reproducible. Escena Viva is an application: it commits it without discussion.
And the flip side: the lock is never edited by hand. It is a generated file. You change it by running npm commands.
npm ci versus npm install
npm ci versus npm installWith the lock on the table, the module's key comparison:
npm install |
npm ci |
|
|---|---|---|
Reads package.json |
Yes | Yes |
Reads package-lock.json |
Yes, if it exists | Mandatory: no lock, no run |
| Modifies the lock | Yes, when needed | Never |
| If the lock does not match the manifest | Updates it silently | Fails with an error |
An existing node_modules |
Updates it incrementally | Deletes it entirely and reinstalls |
| Speed | Slower (it resolves versions) | Faster (it just installs what is written) |
Accepts npm ci <package> |
Yes, npm install <package> |
Does not exist |
| Intended use | Development, adding dependencies | CI, Docker, production |
The decisive row is the fourth. If someone edits package.json by hand —bumps a range, adds a dependency— and does not run npm install, the lock is left out of sync. In that state, npm install fixes it silently; npm ci digs in its heels:
npm error `npm ci` can only install packages when your package.json and npm error package-lock.json are in sync. Please update your lock file with npm error `npm install` before continuing.
That error is good news: CI has detected that the repository is inconsistent before anything gets deployed. Hence the module's operational rule, which we will reuse in Module 11:
- On your machine:
npm install,npm install <package>,npm update. They modify the lock, and that change is committed to git as part of the work. - In CI, Docker and production:
npm ci(with--omit=devin production). Nevernpm install.
- Lock conflicts in git
It is inevitable: two branches add different dependencies, they get merged, and git presents a conflict in a three-thousand-line generated file.
What you do not do: open the file and pick by hand between <<<<<<< markers. The lock is a coherent graph; resolving it piecemeal produces an inconsistent tree that "almost" works.
What you do: discard the file and regenerate it from the manifests, which are human-readable.
# 1. Resolve the package.json conflict first: it is small and readable.
git checkout --ours package-lock.json # or --theirs: it does not matter, it will be regenerated
npm install # regenerates a lock consistent with the merged manifest
npm ci && npm test # verifies that the result installs and works
git add package.json package-lock.json
git commitAn equally valid alternative: delete the lock and run npm install. It is more aggressive —it can raise versions inside the ranges, since everything is resolved again— so the first option is preferable when you want to touch as little as possible.
A hygiene tip: a new dependency deserves its own commit, with package.json and package-lock.json together and nothing else. That way the conflict, if it comes, is tiny, and code review can really look at what got in (05-06).
npm dedupe and --save-exact
npm dedupe and --save-exactnpm dedupe reorganizes the tree to share as many copies as possible. After several successive installs it is easy to end up with duplicates that are no longer needed:
node_modules/
├── package-a/
│ └── node_modules/utility/ <- 1.4.0
└── package-b/
└── node_modules/utility/ <- 1.5.0If both ranges accept 1.5.0, npm dedupe lifts a single copy to the root and deletes the nested ones. Less disk, less audit surface, and goodbye to the instanceof problem between different copies of the same class.
It modifies node_modules and the lock, so you run it in development and commit the result. Never in production.
--save-exact (alias -E) saves the version without the ^:
Or as a project policy, in the .npmrc from 05-01:
Is it worth it? The honest answer is almost never in an application with a lock, because the lock already pins the real version and the range only expresses which update policy you accept. With ^ plus a lock, npm ci is exactly as deterministic.
Pinning exact versions does pay off in three specific cases:
| Case | Reason |
|---|---|
| Dependency with a history of breaking in minor versions | Less room for surprises |
| Tools that affect generated output (compilers, formatters) | So the output does not change without warning |
| A regulated environment that requires justifying every version change | Every bump is explicit and reviewable |
The cost is real: with exact versions, npm update stops being useful and upgrading means touching the manifest package by package. On a large project that gets delegated to Dependabot or Renovate (05-06).
- The minor update that breaks things
The scenario that justifies everything above. On a Tuesday, Escena Viva's CI fails. Nobody has touched the code since Friday.
The cause: [email protected] was published on Monday. Your manifest says ^17.2.1, CI was not using a lock and npm install brought in the new version. A behavior change the author considered minor —how it interprets quotes in .env values, for example— breaks one of your tests.
What to take from that story:
^ is not a guarantee, it is an expectation. It trusts a third party to classify their own change correctly. Most of them do; getting it wrong is easy, because nobody knows every use of their own library. A change the author sees as a fix may be the very behavior you depended on.
The lock turns that expectation into a fact. With package-lock.json in the repository and npm ci in CI, Tuesday would have installed 17.2.1, just like Friday. The new version would come in the day someone runs npm update deliberately, sees the change in the lock's diff and passes the tests before merging.
The difference is not that the failure disappears: it is who controls when it happens. Without a lock, it ambushes you on a Tuesday morning in production. With a lock, it happens on a branch, with an owner and with tests in front of it.
That is why the correct order for upgrading is always the same:
npm outdated # what is available and of what kind
npm update # raises within the ranges: safe
npm test # verify (Module 9)
git add package-lock.json && git commit -m "Update minor dependencies"To jump a major version, the process is different and not automatable: read the release notes, change the range by hand, adapt the code and test. A separate branch, always.
- From
0.1.0 to 1.0.0: versioning Escena Viva
0.1.0 to 1.0.0: versioning Escena VivaEscena Viva was born at 0.1.0 because its API was moving. With Module 4 finished, it has a public, stable HTTP API —GET /api/events, POST /orders—, a settled domain and a client (the front end in public/) that depends on it. It is time for the jump.
npm version does three things, not one: it changes version in package.json and in package-lock.json, it creates a git commit with that change (with 1.0.0 as the default message) and it creates an annotated git tag, v1.0.0, which is the only thing it prints to the screen.
It also accepts bumps by name, which is how it is used day to day:
npm version patch # 1.0.0 -> 1.0.1
npm version minor # 1.0.1 -> 1.1.0
npm version major # 1.1.0 -> 2.0.0
npm version 1.1.0-beta.1 --preid=betaIf you do not want the commit or the tag —because your publishing flow generates them another way— there is --no-git-tag-version.
What the jump to 1.0.0 really means in Escena Viva:
Before (0.x) |
After (1.x) |
|---|---|
| Any minor version could break | Only a jump to 2.0.0 can break |
^0.1.0 ranges accepted patches only |
^1.0.0 accepts the whole 1.x series |
| Changing the API was routine | Changing the API requires a migration plan |
Concretely, from now on: removing a field from the GET /api/events response is major; adding a new field is minor; changing the meaning of an error.appCode in the Module 4 error table is major; fixing a badly computed capacity calculation is patch.
That commitment is the same one you have been demanding from dotenv throughout this lesson. Here you switch sides of the table, and that is why the module continues towards publishing.
Common Mistakes and Tips
- Adding
package-lock.jsonto.gitignore. It is the most expensive mistake in the module: it destroys reproducibility. What gets ignored isnode_modules/, never the lock. - Resolving lock conflicts by hand. It produces incoherent trees that fail in strange ways. Regenerate it with
npm install. - Using
npm installin CI. It can modify the lock during the build and deploy something different from what you tested.npm ci, always. - Expecting
npm updateto bump a major version. It never does, by design. Nor minors in0.xpackages, because of the^0.x.yrule. - Publishing
1.0.0without meaning to promise stability. If your API is still moving, stay on0.x: it is honest information for whoever uses you. - Putting
*orlatestas a range. Any major version gets in without warning. There is no case where it pays off. - Tip: review the lock's diff. In a code review, a lock that changes while
package.jsondoes not deserves a question. - Tip:
npm view <package> versions --jsonlists every published version. Useful for seeing the release cadence before adopting a dependency.
Exercises
Exercise 1. Resolve ranges by hand.
A package has these versions published: 0.9.0, 1.0.0, 1.2.0, 1.2.5, 1.9.0, 2.0.0-beta.1, 2.0.0, 2.1.0. Say what each of these ranges would install: ^1.2.0, ~1.2.0, 1.2.x, >=1.0.0 <2, ^2.0.0, *, 1.2.0 - 1.9.0. Then repeat the exercise assuming the published versions are 0.1.0, 0.2.0, 0.2.5, 0.3.0 and the range is ^0.2.0.
Exercise 2. See the lock in action.
In Escena Viva, run npm ls dotenv and note the version. Open package-lock.json and find its version, resolved and integrity. Now delete node_modules, run npm ci and check that the version is identical. Then edit package.json changing the range to ^17.0.0 without running npm install and launch npm ci. Explain what happens and why it is the desirable behavior.
Exercise 3. Version Escena Viva.
Run npm version 1.0.0 with a clean git tree. Check with git log -1 and git tag exactly what it created. Then decide which kind of bump (patch, minor or major) each change calls for: (a) adding the accessibleVenue field to the GET /api/events response; (b) fixing the available-seats calculation, which was subtracting wrongly; (c) renaming priceCents to amountCents in the API response; (d) changing LOW_CAPACITY_THRESHOLD from 20 to 15; (e) requiring Node 26 in engines.
Solutions
Solution 1. First part:
| Range | Installs | Reason |
|---|---|---|
^1.2.0 |
1.9.0 |
>=1.2.0 <2.0.0; the highest of the 1 series |
~1.2.0 |
1.2.5 |
>=1.2.0 <1.3.0; patches only |
1.2.x |
1.2.5 |
Equivalent to ~1.2.0 |
>=1.0.0 <2 |
1.9.0 |
Explicitly excludes the 2 series |
^2.0.0 |
2.1.0 |
Not 2.0.0-beta.1: pre-releases do not enter normal ranges |
* |
2.1.0 |
The highest stable one |
1.2.0 - 1.9.0 |
1.9.0 |
The hyphen includes both ends |
Second part: ^0.2.0 installs 0.2.5, not 0.3.0. That is the ^0.x.y rule: with the major at zero, the caret protects the minor number too, because 0.3.0 is considered potentially incompatible. This is the answer most people get wrong.
Solution 2. After npm ci, npm ls dotenv shows exactly the same version: the lock rules, not the range.
When you change the range to ^17.0.0 without installing, npm ci fails:
npm error `npm ci` can only install packages when your package.json and npm error package-lock.json are in sync. npm error Invalid: lock file's [email protected] does not satisfy dotenv@^17.0.0
It is desirable because being out of sync is a real repository error, not a detail. Someone touched the manifest without regenerating the lock, so nobody knows which tree was meant to be deployed. npm install would paper over it silently and deploy a resolution nobody has reviewed; npm ci stops the deployment and forces it to be fixed on a branch. The fix is running npm install and committing the resulting lock.
Solution 3. npm version 1.0.0 produces:
$ git log -1 --oneline a1b2c3d 1.0.0 $ git tag v1.0.0 $ git show --stat HEAD package.json | 2 +- package-lock.json | 4 ++--
Classification of the changes:
| Change | Bump | Reason |
|---|---|---|
(a) Adding accessibleVenue |
Minor → 1.1.0 |
It adds information; a client that ignores it keeps working |
| (b) Fixing available seats | Patch → 1.0.1 |
It fixes a bug without changing the shape of the response |
(c) Renaming to amountCents |
Major → 2.0.0 |
Every client that read priceCents breaks |
(d) LOW_CAPACITY_THRESHOLD from 20 to 15 |
Minor → 1.1.0 |
It changes observable behavior without breaking contracts; if that constant were documented as part of the public API, it would be major |
| (e) Requiring Node 26 | Major → 2.0.0 |
It shuts out environments that used to work |
Case (d) is the interesting one: the answer depends on whether that constant is part of the public contract. That ambiguity is exactly the hard part of semver, and the reason a clear CHANGELOG is worth as much as the numbers.
Conclusion
You can now read the language the whole ecosystem uses to communicate. SemVer turns three numbers into a promise: PATCH fixes, MINOR adds without breaking, MAJOR breaks. Pre-release versions are always lower than their stable counterpart and do not enter normal ranges. And from the range table you take away two shapes: ~ lets the last number rise, ^ the last non-zero-from-the-left one —hence ^0.2.3 accepting patches only, the rule almost nobody knows and the one that explains why npm update sometimes seems to do nothing.
But the underlying lesson is that ^ is an expectation, not a guarantee: it depends on a third party classifying their own change correctly. What turns that expectation into a fact is package-lock.json, with its exact tree, its resolved versions, its URLs and above all its SHA-512 integrity, which makes the install fail if the downloaded content is not byte for byte what was recorded. That is why it always goes into the repository, why it is never edited by hand, and why its git conflicts are resolved by regenerating it rather than picking between markers.
From that comes the operational rule that will stay with you to the end of the course: npm install on your machine —it modifies the lock, and that change gets committed and reviewed— and npm ci in CI, in Docker and in production, which requires the lock, honors it to the letter, deletes node_modules and fails if the manifest and the lock do not match. That failure is good news: it stops an inconsistent deployment before it reaches anyone.
Escena Viva is now 1.0.0, with its v1.0.0 git tag created by npm version, and that number is a commitment: from today on, removing a field from the API or changing the meaning of an error.appCode forces a bump to 2.0.0.
In the next lesson, npm Scripts and Project Automation, we fill the manifest's last gap. The scripts field is about to become the project's single interface —npm start, npm run dev, npm run report, npm test— so nobody has to read the README to guess a command. And along the way you will discover why a tool you never installed globally works anyway: the PATH extended with node_modules/.bin.
Node.js Course: From Beginner to Advanced
Module 1: Introduction to Node.js
- What Is Node.js?
- Installing and Setting Up the Environment
- Your First Node.js Program
- The Node.js REPL
- Modern JavaScript for Node.js
- The Course Project: the Escena Viva Platform
Module 2: Core Concepts
- Node.js Architecture
- The Event Loop
- Callbacks and Asynchronous Programming
- Promises and async/await
- Events and EventEmitter
- CommonJS Modules and require()
- ES Modules and Interoperability
Module 3: File System and I/O
- Reading and Writing Files
- The fs Module in Depth
- Cross-Platform Paths with the path Module
- Working with Streams
- Transform Streams and pipeline
- Buffers and Binary Data
Module 4: HTTP and Web Servers
- Creating a Simple HTTP Server
- Handling Requests and Responses
- Manual Routing
- Serving Static Files
- Receiving Data: Request Bodies and JSON
- Consuming External APIs from Node.js
Module 5: NPM and Package Management
- Introduction to NPM and package.json
- Installing and Using Packages
- Semantic Versioning and package-lock
- npm Scripts and Project Automation
- Creating and Publishing Packages
- Dependency Security and Maintenance
Module 6: The Express.js Framework
- Introduction to Express.js
- Setting Up an Express Application
- Routing in Express
- Middleware
- Essential Third-Party Middleware
- Input Data Validation
- Error Handling
Module 7: Databases and ORMs
- Introduction to Databases
- Using MongoDB with Mongoose
- CRUD Operations
- Relationships, Population and Advanced Queries
- Using SQL Databases with Sequelize
- Migrations, Transactions and Seed Data
Module 8: Authentication and Authorization
- Introduction to Authentication
- User Registration and Password Hashing
- Sessions and Cookies with Passport.js
- Authentication with JWT
- Role-Based Access Control
- API Security Best Practices
Module 9: Testing and Debugging
- Introduction to Testing
- Unit Testing with Mocha and Chai
- Test Doubles with Sinon
- Integration Testing
- Coverage and Test Automation
- Debugging Node.js Applications
Module 10: Advanced Topics
- The Cluster Module
- Worker Threads
- Caching and Job Queues with Redis
- Performance Optimization
- Building RESTful APIs
- GraphQL with Node.js
Module 11: Deployment and DevOps
- Configuration and Environment Variables
- Logging and Monitoring in Production
- Using PM2 for Process Management
- Packaging with Docker
- Deploying to Heroku and Other PaaS
- Continuous Integration and Deployment
