W02 Assignment: Part 3 - Implement the Plan
This part represents the bulk of the work for Week 02 and may take a significant amount of time to complete. Work through one issue at a time, create pull requests for code changes, and test through Swagger as you go.
Overview
- Purpose: Complete the assigned portion of this week's project.
- Task: Follow the instructions below and submit the required work.
In this part of the assignment, you will implement the two issues you created in Part 2. You will extend your Week 01 Books API with write operations, an authors collection, a relationship between books and authors, Swagger documentation, and deployment verification.
Implementation Strategy
Follow the same GitHub workflow you practiced in Week 01: create a branch for an issue, implement the work, test it, open a pull request, merge it to main, and confirm the issue is closed.
Use your spec from Part 1 (as included in your GitHub issues) as the source of truth for exact route names, required fields, status codes, and response bodies.
Pull request pattern
For each coding issue, follow this pattern:
- Switch to the latest
mainbranch and pull the newest code. - Create a focused feature branch for the issue.
- Implement the work and test it locally.
- Commit and push the branch.
- Open a pull request into
main. - In the pull request description, include
Resolves #...,Closes #..., orFixes #...with the correct issue number. - Include the verified Test Plan steps in the pull request description.
- Merge the pull request, then update your local
mainbranch and delete the local feature branch.
Code reviews for individual work
For Week 02, this is still an individual assignment so your pull requests do not require teammate code reviews. You still need to use pull requests so your work is organized and connected to GitHub issues.
Beginning in Week 03, you will be working on a team project and pull requests will require code reviews from your teammates.
Plan your work
In Part 2, the book issue was listed first because Part 1 gave you a complete book spec. For implementation, it makes more sense to complete the author issue first. The book API needs real author records before it can validate authorId values, and the book delete/update tests are easier when the author API already exists.
With this in mind, for this assignment, it is best to complete the issues in this order:
- Implement Author Collection and API.
- Update Book Collection to reference authors and finish Book API.
The relationship creates a dependency
Books will store an authorId value that points to an author document. Because of that, you should create and test the author API before you finish the book API.
Issue: Implement Author Collection and API
Even though you wrote your own author spec, this section walks you through the author implementation in detail. Use your spec from Part 1 as the source of truth when your field names, status codes, or error messages differ from the examples.
Create a branch
git switch main
git pull origin main
git switch -c implement-author-api
Review your author issue and spec
Open the GitHub issue titled Implement Author Collection and API. Review the details of the work you need to do and how you will verify it.
Create initial author data
Sign in to MongoDB Atlas in your browser, open Data Explorer, and select the database used by your Books API.
- In the Connections sidebar, select or hover over the database and open the control to create a collection.
- Name the new collection
authors, then click Create Collection. - Select the
authorscollection, click Insert Document, and switch to JSON mode if the field-by-field editor is displayed. - Add at least three author documents. (You can use AI to help create some test authors, use the author names for the books you already have, or simply make some up.)
- Use custom ids such as
a1,a2, anda3. - Use the fields required by the assignment:
id,name, andbirthYear. - Click Insert after entering each document.
The following is an example document:
{
"id": "a1",
"name": "Jane Austen",
"birthYear": 1775
}
Project structure
For the authors model and controller functions, you will follow the same pattern you used in your Week 01 book files.
src/models/authors.js: Contains the author model functions.src/controllers/authors.js: Contains the author controller functions.src/router.js: Contains the author routes.
Build one route end to end first
A good development pattern is to get one small piece working all the way through before building everything else. For the author API, start with GET /authors. You will create the model function, controller function, route, Swagger documentation, and local Swagger test for that one route. Once that works, you can repeat the same pattern for the remaining routes with more confidence.
Add the getAllAuthors model function
Create the file src/models/authors.js and add a function that reads all documents from the authors collection.
import { getDb } from '../db/connect.js';
const getAllAuthors = async () => {
const db = getDb();
const collection = db.collection('authors');
const authors = await collection.find({}).toArray();
return authors;
};
export { getAllAuthors };
This follows the same pattern as your book model function. First, you get a reference to the database and collection, then you query the collection and return the results.
Add the getAllAuthors controller function
Create the file src/controllers/authors.js and add a function that calls the model function and returns a safe response.
import { getAllAuthors as getAllAuthorsFromDb } from '../models/authors.js';
const getAllAuthors = async (req, res) => {
try {
const authors = await getAllAuthorsFromDb();
return res.status(200).json(authors);
} catch (error) {
return res.status(500).json({ message: 'Unable to retrieve authors.' });
}
};
export { getAllAuthors };
Connect the GET /authors route
In src/router.js, import the authors controller.
import { getAllAuthors } from './controllers/authors.js';
And include the following among your other routes.
router.get('/authors', getAllAuthors);
Document GET /authors in Swagger
Add a Swagger comment for the route. Adjust file placement to match the pattern from your existing route file.
/**
* @openapi
* /authors:
* get:
* summary: Get all authors
* tags:
* - Authors
* responses:
* 200:
* description: A list of authors
* 500:
* description: Unable to retrieve authors
*/
router.get('/authors', getAllAuthors);
Generate Swagger, test, and lint
- Run your Swagger generation command:
npm run swagger - Start your app:
npm run dev - In a browser, open
http://127.0.0.1:3000/api-docslocally. - Find
GET /authors, click Try it out, and run the request. - Confirm that it returns status
200and an array of authors. - Run the linter:
npm run lint - If there are lint errors, fix them. If ESLint can safely fix some issues automatically, you may run:
npm run lint:fix npm run lint
Fix errors before proceeding
Make sure that everything is working correctly before moving on to the next steps. If you have an error, do not continue to the next step until it is fixed.
Use the same pattern for the remaining author routes
Now that one route works end to end, repeat the process for the remaining author routes as a group:
GET /authors/:idPOST /authorsPUT /authors/:idDELETE /authors/:id
AI can help, but you are responsible
This is a good place to use AI to generate draft code. Then review every line in detail. Make sure the code follows your spec, matches the patterns in your project, and follows the CSE 341 coding standards.
Create the remaining model functions
Follow the same pattern as the existing functions. You are welcome to write the code yourself or to use AI.
If you use AI, you might use a prompt such as the following:
Please generate model functions for:
- getAuthorById(id)
- createAuthor(author)
- updateAuthor(id, author)
- deleteAuthor(id)
- authorHasBooks(id)
My author documents use these fields:
- id
- name
- birthYear
Follow the same pattern as the getAllAuthors function which is:
- import { getDb } from '../db/connect.js';
- inside each function, call const db = getDb();
- then call const collection = db.collection('authors') or db.collection('books');
- use custom string ids, not MongoDB _id values.
- export functions using named exports.
Sample code (click to expand)
The following shows the model functions for the authors collection:
import { getDb } from '../db/connect.js';
const getAllAuthors = async () => {
const db = getDb();
const collection = db.collection('authors');
const authors = await collection.find({}).toArray();
return authors;
};
const getAuthorById = async (id) => {
const db = getDb();
const collection = db.collection('authors');
const author = await collection.findOne({ id });
return author;
};
const createAuthor = async (author) => {
const db = getDb();
const collection = db.collection('authors');
await collection.insertOne(author);
return author;
};
const updateAuthor = async (id, author) => {
const db = getDb();
const collection = db.collection('authors');
await collection.updateOne({ id }, { $set: author });
return { id, ...author };
};
const deleteAuthor = async (id) => {
const db = getDb();
const collection = db.collection('authors');
const result = await collection.deleteOne({ id });
return result;
};
const authorHasBooks = async (id) => {
const db = getDb();
const collection = db.collection('books');
const count = await collection.countDocuments({ authorId: id });
return count > 0;
};
export { getAllAuthors, getAuthorById, createAuthor, updateAuthor, deleteAuthor, authorHasBooks };
Create the remaining controller functions
Follow the same pattern as the existing functions. You are welcome to write the code yourself or to use AI.
If you use AI, you might use a prompt such as the following:
Please generate controller functions for:
- getAuthorById
- createAuthor
- updateAuthor
- deleteAuthor
Follow the same pattern as the getAllAuthors function which is:
- import the model functions from '../models/authors.js'
- validate required fields before writing to MongoDB
- return safe JSON error messages
- do not expose raw database errors to the client
- follow the status codes from my spec
- for deleting an author who still has books, follow the behavior in my spec
Here is my spec:
GET /authors
- Purpose: Return all authors.
- Success: 200 with an array of author objects.
- Errors: 500 if an unexpected server or database error occurs.
GET /authors/:id
- Purpose: Return one author by custom id.
- Success: 200 with the matching author object.
- Errors: 404 if no author exists with that id; 500 if an unexpected server or database error occurs.
POST /authors
- Purpose: Create a new author.
- Request body:
{
"id": "a4",
"name": "Example Author",
"birthYear": 1980
}
- Success: 201 with the newly created author object.
- Errors: 400 if a required field is missing; 400 if the id already exists; 500 if an unexpected server or database error occurs.
PUT /authors/:id
- Purpose: Update an existing author.
- Request body:
{
"name": "Updated Author",
"birthYear": 1981
}
- Success: 200 with the updated author object.
- Errors: 400 if a required field is missing; 404 if no author exists with that id; 500 if an unexpected server or database error occurs.
DELETE /authors/:id
- Purpose: Delete an existing author.
- Success: 204 with no response body.
- Errors: 404 if no author exists with that id; 409 if the author still has books; 500 if an unexpected server or database error occurs.
Detailed AI Prompts
This example prompt is very detailed which helps the AI understand the exact requirements. When you wrote your spec, you took the time to think through the route behavior, required fields, status codes, and error cases. Now you can use that so AI has clearer instructions and is much more likely to generate correct code that works the way you expect.
Sample code (click to expand)
The following shows the controller functions for the authors collection:
import {
getAllAuthors as getAllAuthorsFromDb,
getAuthorById as getAuthorByIdFromDb,
createAuthor as createAuthorFromDb,
updateAuthor as updateAuthorFromDb,
deleteAuthor as deleteAuthorFromDb,
authorHasBooks,
} from '../models/authors.js';
const getAllAuthors = async (req, res) => {
try {
const authors = await getAllAuthorsFromDb();
return res.status(200).json(authors);
} catch (error) {
return res.status(500).json({ message: 'Unable to retrieve authors.' });
}
};
const getAuthorById = async (req, res) => {
try {
const { id } = req.params;
const author = await getAuthorByIdFromDb(id);
if (!author) {
return res.status(404).json({ message: 'Author not found.' });
}
return res.status(200).json(author);
} catch (error) {
return res.status(500).json({ message: 'Unable to retrieve author.' });
}
};
const createAuthor = async (req, res) => {
try {
const { id, name, birthYear } = req.body;
if (!id || !name || birthYear === undefined) {
return res.status(400).json({ message: 'Missing required author fields.' });
}
const existingAuthor = await getAuthorByIdFromDb(id);
if (existingAuthor) {
return res.status(400).json({ message: 'Author id already exists.' });
}
const createdAuthor = await createAuthorFromDb({ id, name, birthYear });
return res.status(201).json(createdAuthor);
} catch (error) {
return res.status(500).json({ message: 'Unable to create author.' });
}
};
const updateAuthor = async (req, res) => {
try {
const { id } = req.params;
const { name, birthYear } = req.body;
if (!name || birthYear === undefined) {
return res.status(400).json({ message: 'Missing required author fields.' });
}
const existingAuthor = await getAuthorByIdFromDb(id);
if (!existingAuthor) {
return res.status(404).json({ message: 'Author not found.' });
}
const updatedAuthor = await updateAuthorFromDb(id, { name, birthYear });
return res.status(200).json(updatedAuthor);
} catch (error) {
return res.status(500).json({ message: 'Unable to update author.' });
}
};
const deleteAuthor = async (req, res) => {
try {
const { id } = req.params;
const existingAuthor = await getAuthorByIdFromDb(id);
if (!existingAuthor) {
return res.status(404).json({ message: 'Author not found.' });
}
if (await authorHasBooks(id)) {
return res.status(409).json({ message: 'Author cannot be deleted because they still have books.' });
}
await deleteAuthorFromDb(id);
return res.status(204).send();
} catch (error) {
return res.status(500).json({ message: 'Unable to delete author.' });
}
};
export { getAllAuthors, getAuthorById, createAuthor, updateAuthor, deleteAuthor };
Create the remaining author routes
Follow the same pattern as the existing GET /authors route. You are welcome to write the route lines yourself or to use AI.
You could use something like the following for your AI prompt:
Please generate the following routes:
- GET /authors
- GET /authors/:id
- POST /authors
- PUT /authors/:id
- DELETE /authors/:id
Here are the author controller functions to connect:
- getAllAuthors
- getAuthorById
- createAuthor
- updateAuthor
- deleteAuthor
Follow the same pattern as my existing GET /authors route.
Make sure to include the appropriate swagger documentation comments for each route based on the functionality of the controllers.
For POST /authors and PUT /authors/:id, the Swagger comments must include a requestBody section for incoming application/json data, including the expected fields and an example JSON body.
AI Tip
If you are using AI directly in VS Code, it should be able to see the controllers in the other file. If you are using a chat interface in a browser, copy and paste the relevant code from your project so the AI has the right context.
Sample code (click to expand)
The following shows one way to connect all author routes in src/router.js:
import {
getAllAuthors,
getAuthorById,
createAuthor,
updateAuthor,
deleteAuthor,
} from './controllers/authors.js';
const router = express.Router();
/**
* @openapi
* /authors:
* get:
* summary: Get all authors
* tags:
* - Authors
* responses:
* 200:
* description: A list of authors
* 500:
* description: Unable to retrieve authors
*/
router.get('/authors', getAllAuthors);
/**
* @openapi
* /authors/{id}:
* get:
* summary: Get author by ID
* tags:
* - Authors
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: The author ID.
* responses:
* 200:
* description: A single author
* 404:
* description: Author not found
* 500:
* description: Unable to retrieve author
*/
router.get('/authors/:id', getAuthorById);
/**
* @openapi
* /authors:
* post:
* summary: Create an author
* tags:
* - Authors
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - id
* - name
* - birthYear
* properties:
* id:
* type: string
* name:
* type: string
* birthYear:
* type: number
* example:
* id: a4
* name: Example Author
* birthYear: 1980
* responses:
* 201:
* description: Author created
* 400:
* description: Missing required author fields or author ID already exists
* 500:
* description: Unable to create author
*/
router.post('/authors', createAuthor);
/**
* @openapi
* /authors/{id}:
* put:
* summary: Update an author by ID
* tags:
* - Authors
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: The author ID.
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - birthYear
* properties:
* name:
* type: string
* birthYear:
* type: number
* example:
* name: Updated Author
* birthYear: 1981
* responses:
* 200:
* description: Author updated
* 400:
* description: Missing required author fields
* 404:
* description: Author not found
* 500:
* description: Unable to update author
*/
router.put('/authors/:id', updateAuthor);
/**
* @openapi
* /authors/{id}:
* delete:
* summary: Delete an author by ID
* tags:
* - Authors
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: The author ID.
* responses:
* 204:
* description: Author deleted
* 404:
* description: Author not found
* 409:
* description: Author cannot be deleted because they still have books
* 500:
* description: Unable to delete author
*/
router.delete('/authors/:id', deleteAuthor);
Review, test, and lint the full author API
- Review every generated model, controller, route, and Swagger line yourself.
- Compare the generated code with your spec and the course coding standards.
- Run
npm run swagger. - Start the app with
npm run devand test every author route locally from/api-docs. - Run
npm run lintand fix all linting errors before committing.
Author Test Plan:
- GET /authors returns an array with the status code from your spec.
- GET /authors/:id returns an author for an existing id.
- GET /authors/:id handles a missing author according to your spec.
- POST /authors validates input and creates an author.
- POST /authors handles a duplicate id according to your spec.
- PUT /authors/:id validates input and updates an existing author.
- PUT /authors/:id handles a missing author according to your spec.
- DELETE /authors/:id handles successful deletion.
- DELETE /authors/:id handles missing authors and authors with books according to your spec.
- All author routes are documented in Swagger.
- All author routes can be tested locally from /api-docs.
- npm run lint passes.
Create the author pull request, deploy, and test
- Commit and push your author API branch.
- Open a pull request into
main. - Include
Resolves #...,Closes #..., orFixes #...with the number for your author issue. - Paste the verified author Test Plan into the pull request description.
- Merge the pull request and delete the branch at GitHub.
- Confirm Render deployed the latest commit from
main. If needed, use Manual Deploy and select From last commit. - Use the deployed
/api-docspage to test every author route. - On your computer, switch back to the
mainbranch and pull the latest changes. Then, delete the local branch for this feature.
Issue: Update Book Collection to reference authors and finish Book API
After the author API works, return to the book issue. The author routes now give you real author ids to reference and test against.
Create a branch
git switch main
git pull origin main
git switch -c update-book-api
Use the completed book issue as your checklist
Open the GitHub issue titled Update Book Collection to reference authors and finish Book API. The issue already includes the full book spec and Test Plan. Use that issue to guide the implementation.
Update book data and model logic
- Update each existing book document so it includes an
authorIdfield. - Make sure every book
authorIdmatches an existing authorid. - Add or update model functions for reading, creating, updating, and deleting books.
- Add a model helper that checks whether an author exists before a book is created or updated.
{
"id": "b1",
"authorId": "a1",
"title": "Pride and Prejudice",
"publicationDate": "1813-01-28"
}
Reference, not copy
The book should store the author's id, not the whole author document and not just the author's name. This keeps the relationship clear: books.authorId points to authors.id.
Build the book controller and routes
- Validate
id,authorId,title, andpublicationDatewhen creating a book. - Validate
authorId,title, andpublicationDatewhen updating a book. - Reject create and update requests when
authorIddoes not match an existing author. - Return
404when a requested book does not exist. - Add
POST /books,PUT /books/:id, andDELETE /books/:id.
Document and test the book API
- Document all book routes in Swagger, including the updated book schema with
authorId. - Run
npm run swagger. - Start the app and test every book route locally from
/api-docs. - Make sure to go through each step in your test plan.
- Run
npm run lintand fix all linting errors before committing.
Book Test Plan:
- Existing GET /books responses include authorId.
- Existing GET /books/:id responses include authorId.
- POST /books creates a book and returns status 201.
- POST /books returns 400 when required fields are missing.
- POST /books returns 400 when the submitted id already exists.
- POST /books returns 400 when authorId does not match an existing author.
- PUT /books/:id updates an existing book and returns status 200.
- PUT /books/:id returns 400 when authorId does not match an existing author.
- PUT /books/:id returns 404 when the book does not exist.
- DELETE /books/:id deletes an existing book and returns status 204.
- DELETE /books/:id returns 404 when the book does not exist.
- All book routes are documented in Swagger.
- All book routes can be tested locally and from the deployed /api-docs page.
Create the book pull request, deploy, and test
- Commit, push, and open a pull request for the book issue.
- Include
Resolves #...,Closes #..., orFixes #...with the number for your book issue. - Paste the verified book Test Plan into the pull request description.
- Merge the pull request and delete the branch at GitHub.
- Confirm Render deployed the latest commit from
main. If needed, use Manual Deploy and select From last commit. - Use the deployed
/api-docspage to test every book route. - Retest at least one author route to confirm the author API still works.
- On your computer, switch back to the
mainbranch and pull the latest changes. Then, delete the local branch for this feature.
Next Step
Continue to Part 4 - Reflect.
Other Links:
- Return to: Week Overview | Course Home