# Week 3 Study Plan: MongoDB, Express, and API Tests

Use this guide with the completed code in `src/`. It starts with the ideas
behind the code, then walks through each file and gives you small checks to
make sure you can explain what is happening.

## What you should be able to do

By the end of this week, you should be able to:

1. explain how a MongoDB collection and document compare with a SQL table and row;
2. connect a TypeScript program to a MongoDB database;
3. create, read, update, and delete documents;
4. place database functions behind Express routes;
5. recognize a MongoDB `ObjectId` and reject a malformed one;
6. use Vitest, Supertest, and MongoMemoryServer to test an API without changing a real database;
7. use test hooks to give every test a clean starting state.

## 1. Start with the database vocabulary

MongoDB is a document-oriented NoSQL database. A useful first comparison is:

| MongoDB | Rough SQL comparison | Example in this project |
| --- | --- | --- |
| database | database | `week3app` or the test database |
| collection | table | `products` |
| document | row | one Duct Tape product |
| field | column | `name`, `price`, or `quantity` |

A product document looks much like a JavaScript object:

```ts
{
  name: 'Duct Tape',
  price: 5.99,
  quantity: 120
}
```

MongoDB adds an `_id` field when the document is inserted. The value is an
`ObjectId`. Its printed form is a string of 24 hexadecimal characters, but the
database value is an `ObjectId`, not an ordinary string.

Checkpoint:

- Which collection stores the records in this app?
- Which part uniquely identifies one product?
- Why must `new ObjectId(id)` be used before searching by `_id`?

## 2. Practice Mongo operations directly

Read `src/db-explore.ts` first. It is a scratch program for learning the
database driver before Express is involved.

The connection sequence is:

```text
MongoClient(uri) -> client.connect() -> client.db(name) -> collection(name)
```

The file includes these four CRUD operations:

| Goal | MongoDB method |
| --- | --- |
| create documents | `insertMany()` |
| read documents | `find()`, `findOne()` |
| update documents | `updateOne()`, `updateMany()` |
| delete documents | `deleteOne()` |

Mongo queries are objects. `{ name: 'Masking Tape' }` means “documents whose
name equals Masking Tape.” `{ price: { $gte: 5 } }` means “documents whose
price is greater than or equal to 5.” The `$set` update operator changes only
the fields named inside it.

The four Now You Try functions demonstrate those ideas:

- `findPricierTest()` uses `$gte` to find products costing at least $5.
- `updatePriceTest()` changes only Scotch Tape's price.
- `deleteByNameTest()` deletes a document using a name query.
- `markOnSaleTest()` uses `updateMany()` to add `onSale: true` to every
  product costing less than $5.

Exercise:

1. Start local MongoDB.
2. Uncomment `await addProducts()` in `main()` and run `npm run explore` once.
3. Comment it again so repeated saves do not create duplicates.
4. Run each Now You Try function one at a time and print the result.
5. After each operation, print `getAllProducts()` and predict the output before
   looking at it.

## 3. Separate database work from HTTP work

`src/db.ts` is the database layer used by the application. It keeps the
MongoDB driver details in one place. Express routes do not need to know how a
client is created or which collection method performs an update.

The public functions form a small interface:

| Function | Input | Output |
| --- | --- | --- |
| `connect` | URI and database name | an open connection |
| `getAllProducts` | none | every product document |
| `getProduct` | string ID | one document or `null` |
| `addProduct` | a `Product` | the new `ObjectId` |
| `updateProduct` | ID and partial changes | number of matched documents |
| `deleteProduct` | ID | number of deleted documents |

`ProductUpdate` makes each property optional. That matters because a PATCH
request can change only `price` while leaving `name` and `quantity` alone.

`_clearProducts()` is intentionally marked as a test helper. Calling it in the
running app would erase the whole products collection.

Checkpoint:

- Why does `updateProduct()` return `matchedCount` instead of the full driver result?
- How does a route use `matchedCount === 0` to choose an HTTP status?
- Why does `connect()` accept its URI and database name as arguments?

## 4. Map CRUD operations to HTTP routes

`src/product-router.ts` maps the database functions to an HTTP API:

| Request | Database action | Successful response |
| --- | --- | --- |
| `GET /products` | read all | `200` and a JSON array |
| `GET /products/:id` | read one | `200` and one JSON document |
| `POST /products` | create | `201` and `{ "id": ... }` |
| `PATCH /products/:id` | update selected fields | `200` |
| `DELETE /products/:id` | delete one | `200` |

A valid-looking ID that is absent from the database produces `404 Not Found`.
A malformed ID produces `400 Bad Request`. These are different failures: the
first identifies no document, while the second is not a usable MongoDB ID.

`src/validate-id.ts` handles malformed IDs before a route calls the database.
Without that middleware, `new ObjectId('badID123')` throws and looks like a
server failure. `ObjectId.isValid()` lets the app report the client's input
error accurately.

`src/app.ts` first registers `express.json()`, then mounts the router at
`/products`. The parser must run first so a POST or PATCH handler can read
`req.body`.

`src/index.ts` opens the real database connection before the server listens.
Tests import `app.ts` instead, connect to their own temporary database, and do
not run `index.ts`. This is why the app and server are split into two files.

Exercise:

1. Trace `PATCH /products/abc...` from `app.ts` to the response.
2. Write down each function it passes through.
3. Repeat with a malformed ID and notice where the path stops.

## 5. Understand the test setup

The tests use three tools with different jobs:

- Vitest runs test files and provides `describe`, `it`, `expect`, and hooks.
- Supertest sends HTTP requests directly to the Express app.
- MongoMemoryServer starts a temporary real MongoDB process for the tests.

The hooks establish a predictable lifecycle:

```text
beforeAll:  start temporary MongoDB and connect once
beforeEach: empty the products collection
test:       arrange data, send request, check response and state
afterAll:   disconnect and stop temporary MongoDB
```

Cleaning before every test prevents one test's documents from changing a
later test's result. Stopping MongoDB in `afterAll` prevents the test process
from hanging.

The main matchers in this project are:

- `toBe()` for exact primitive values such as a status code;
- `toEqual()` for arrays or objects with the same contents;
- `toMatch()` for the 24-character ID pattern;
- `toHaveLength()` for array size;
- `toContain()` and `.not.toContain()` for membership.

The PATCH test checks more than the changed price. It also checks that `name`
and `quantity` stayed the same. That is what proves the code performs a
partial update.

The DELETE test verifies both the response and the database state by trying to
read the deleted product and by checking the final product list.

## 6. Suggested study session

### First 20 minutes: vocabulary and data flow

- Draw the path from an HTTP request to MongoDB and back.
- Label the app, router, database layer, collection, and response.
- Explain collection, document, field, and `ObjectId` aloud.

### Next 25 minutes: direct Mongo practice

- Work through `db-explore.ts` one function at a time.
- Predict what each query matches.
- Verify the resulting documents in `getAllProducts()` or MongoDB Compass.

### Next 25 minutes: API code

- Read `db.ts`, then `product-router.ts`, then `app.ts`, then `index.ts`.
- For each route, identify its input, database function, success status, and
  missing-resource behavior.

### Final 20 minutes: tests

- Run `npm test`.
- Temporarily change one expected status to the wrong value and read the
  failure. Restore it afterward.
- Explain why every hook is `beforeAll`, `beforeEach`, or `afterAll`.
- Add one extra GET-by-ID success test on your own.

## Final self-check

You are ready to move on when you can answer these without opening the code:

1. What is the difference between a MongoDB document and a JavaScript object?
2. Why is a string ID converted to an `ObjectId`?
3. What do `$gte` and `$set` do?
4. Why does PATCH use optional fields?
5. Why do malformed and missing IDs have different status codes?
6. Why does each test need a clean collection?
7. Why does the test suite import `app` instead of `index`?

Useful commands:

```bash
npm run typecheck
npm run build
npm test
npm run explore
npm run dev
```
