Share
Explore

f24 Full Stack Development Tuesday October 1

Note: The conclusion of this lab is an API Server. You can build the HTML client in this following lab to work with the Server:

Let’s start by making the College Enrollment System working with JSON in MONGO

A high level review of SQL, as we will now proceed from JSON predication joins to JSON projections
image.png



Refresher on basic SQL Concepts including the PREDICATE JOIN between Tables

Cartoon Superheros: Schema and Model

Today’s learning topics:

What we have done so far...

Adding Mongo Atlas to an Express project
Installing Mongoose
Adding the Mongo Connection String
Creating a Model
Creating a Schema
Creating an Express GET endpoint that performs a query (.find, .findById)
Insert a document
Update a document
Delete a document
*** -> Working with more than one collection (example: Employees and Vehicles, Books and Authors, etc) Given that I have already taught {Adding Mongo Atlas to an Express project
Installing Mongoose
Adding the Mongo Connection String
Creating a Model
Creating a Schema} ### although more reinforcement and exposure for our new learners is always beneficial
A learning script for Creating an Express GET endpoint that performs a query (.find, .findById)
Insert a document
Update a document
Delete a document
*** -> Working with more than one collection (example: Employees and Vehicles, Books and Authors, etc)


The BOOKSTORE Mongo DB Express Application
megaphone

Let's create an example project working with two collections:

"Books" and "Authors".

This will allow us to demonstrate CRUD operations and working with multiple collections.

MongoDB Operations with Express

This lab covers all the basic CRUD operations and demonstrates working with multiple collections (Books and Authors).
Here's a breakdown of what each part does:
We define schemas and models for both Author and Book.
The Book schema includes a reference to Author.
GET endpoint to find all books:
Uses Book.find() to retrieve all books
Uses .populate('author') to include full author details
GET endpoint to find a book by ID:
Uses Book.findById(req.params.id) to find a specific book
Also populates the author field
POST endpoint to insert a new author:
Creates a new Author document
Saves it to the database
POST endpoint to insert a new book:
Creates a new Book document, including a reference to an author
Saves it to the database
PATCH endpoint to update a book:
Finds the book by ID
Updates the fields that are provided in the request
Saves the updated book
DELETE endpoint to delete a book:
Finds the book by ID
Removes it from the database
This script demonstrates how to work with multiple collections by using references between Books and Authors.
When we retrieve books, we can populate the author field to get the full author details.
To use this in your Express app:
Save this code in a separate file (e.g., bookRoutes.js)
In your main app file:

const bookRoutes = require('./bookRoutes');
app.use('/api', bookRoutes);
This sets up all these routes under the /api path.

GitHUB with this code:

Here's the complete code for bookRoutes.js that you can copy and paste into your project.
This code implements all the CRUD operations for books and authors, including the references between them:
Bookstore Express Routes with MongoDB - To use this in your Express app:
Save this code in a file named bookRoutes.js in your project directory.
In your main app file (e.g., app.js or server.js), add these lines:
const bookRoutes = require('./bookRoutes');
app.use('/api', bookRoutes);
This code sets up all the routes under the /api path.
Here's a breakdown of what each part does:
It defines schemas and models for both Author and Book.
The Book schema includes a reference to Author.
GET /api/books retrieves all books and populates author details.
GET /api/books/:id finds a specific book by ID and populates author details.
POST /api/authors creates a new Author document.
POST /api/books creates a new Book document, including a reference to an author.
PATCH /api/books/:id updates a book's information.
DELETE /api/books/:id removes a book from the database.
This setup demonstrates how to work with multiple collections by using references between Books and Authors. When we retrieve books, we use .populate('author') to get the full author details.
Remember to handle errors appropriately and validate input data in a production environment. Also, ensure that your MongoDB connection is set up correctly in your main application file.

Note the core importance of the router — this is the npm package to which we apply the operations of C R U D
error

// bookRoutes.js

const express = require('express');
const mongoose = require('mongoose');
const router = express.Router();
// Define schemas
const authorSchema = new mongoose.Schema({
name: String,
bio: String
});

const bookSchema = new mongoose.Schema({
title: String,
description: String,
publishedYear: Number,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'Author' }
});

// Create models
const Author = mongoose.model('Author', authorSchema);
const Book = mongoose.model('Book', bookSchema);

// GET all books
router.get('/books', async (req, res) => {
try {
// JOIN between collections happens when you use the POPULATE METHOD:
const books = await Book.find().populate('author');
// remember in express.js res → response back to the requesting web page.
res.json(books);
} catch (err) {
res.status(500).json({ message: err.message });
}
});

// GET a specific book by ID
router.get('/books/:id', async (req, res) => {
try {
const book = await Book.findById(req.params.id).populate('author');
if (!book) {
return res.status(404).json({ message: 'Book not found' });
}
res.json(book);
} catch (err) {
res.status(500).json({ message: err.message });
}
});

// POST a new author
router.post('/authors', async (req, res) => {
const author = new Author({
name: req.body.name,
bio: req.body.bio
});

try {
const newAuthor = await author.save();
res.status(201).json(newAuthor);
} catch (err) {
res.status(400).json({ message: err.message });
}
});

// POST a new book
router.post('/books', async (req, res) => {
const book = new Book({
title: req.body.title,
description: req.body.description,
publishedYear: req.body.publishedYear,
author: req.body.authorId
});

try {
const newBook = await book.save();
res.status(201).json(newBook);
} catch (err) {
res.status(400).json({ message: err.message });
}
});

// PATCH (update) a book
router.patch('/books/:id', async (req, res) => {
try {
const book = await Book.findById(req.params.id);
if (!book) {
return res.status(404).json({ message: 'Book not found' });
}

if (req.body.title) book.title = req.body.title;
if (req.body.description) book.description = req.body.description;
if (req.body.publishedYear) book.publishedYear = req.body.publishedYear;
if (req.body.authorId) book.author = req.body.authorId;

const updatedBook = await book.save();
res.json(updatedBook);
} catch (err) {
res.status(400).json({ message: err.message });
}
});

// DELETE a book
router.delete('/books/:id', async (req, res) => {
try {
const book = await Book.findById(req.params.id);
if (!book) {
return res.status(404).json({ message: 'Book not found' });
}

await book.remove();
res.json({ message: 'Book deleted' });
} catch (err) {
res.status(500).json({ message: err.message });
}
});

module.exports = router;



info

// app.js const express = require('express'); const mongoose = require('mongoose'); const bodyParser = require('body-parser'); require('dotenv').config();

const app = express(); const PORT = process.env.PORT || 3000;
// Middleware app.use(bodyParser.json());
// MongoDB Atlas Connection mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true, }) .then(() => console.log('Connected to MongoDB Atlas')) .catch(err => console.error('Error connecting to MongoDB Atlas:', err));
// Define schemas const authorSchema = new mongoose.Schema({ name: String, birthYear: Number });
const bookSchema = new mongoose.Schema({ title: String, publishYear: Number, author: { type: mongoose.Schema.Types.ObjectId, ref: 'Author' } });
// Create models const Author = mongoose.model('Author', authorSchema); const Book = mongoose.model('Book', bookSchema);
// Routes
// GET all books app.get('/api/books', async (req, res) => { try { const books = await Book.find().populate('author'); res.json(books); } catch (err) { res.status(500).json({ message: err.message }); } });
// GET a book by ID app.get('/api/books/:id', async (req, res) => { try { const book = await Book.findById(req.params.id).populate('author'); if (book == null) { return res.status(404).json({ message: 'Book not found' }); } res.json(book); } catch (err) { res.status(500).json({ message: err.message }); } });
// POST a new author app.post('/api/authors', async (req, res) => { const author = new Author({ name: req.body.name, birthYear: req.body.birthYear });
try { const newAuthor = await author.save(); res.status(201).json(newAuthor); } catch (err) { res.status(400).json({ message: err.message }); } });
// POST a new book app.post('/api/books', async (req, res) => { const book = new Book({ title: req.body.title, publishYear: req.body.publishYear, author: req.body.authorId });
try { const newBook = await book.save(); res.status(201).json(newBook); } catch (err) { res.status(400).json({ message: err.message }); } });
// PATCH (update) a book app.patch('/api/books/:id', async (req, res) => { try { const book = await Book.findById(req.params.id); if (book == null) { return res.status(404).json({ message: 'Book not found' }); }
if (req.body.title != null) { book.title = req.body.title; } if (req.body.publishYear != null) { book.publishYear = req.body.publishYear; } if (req.body.authorId != null) { book.author = req.body.authorId; }
const updatedBook = await book.save(); res.json(updatedBook); } catch (err) { res.status(400).json({ message: err.message }); } });
// DELETE a book app.delete('/api/books/:id', async (req, res) => { try { const book = await Book.findById(req.params.id); if (book == null) { return res.status(404).json({ message: 'Book not found' }); }
await book.deleteOne(); res.json({ message: 'Book deleted' }); } catch (err) { res.status(500).json({ message: err.message }); } });
// Start the server app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });


"Highlights reel" lecture that spotlights how each CRUD (Create, Read, Update, Delete) operation is implemented in our MongoDB with Express lab code.


This will help students focus on the key parts of each operation.
MongoDB with Express: CRUD Operations Highlights
This "highlights reel" lecture provides a concise overview of how each CRUD operation is implemented in the lab code. Here are some key points to emphasize when presenting this to students:
Consistency in structure: Note how each route follows a similar pattern of trying an operation and catching any errors.
Asynchronous operations: All database operations are asynchronous and use async/await for cleaner code.
Error handling: Each operation includes error handling to send appropriate status codes and messages.
Use of Mongoose methods: Point out how we use Mongoose methods like find(), findById(), save(), and deleteOne() to interact with the database.
Working with related data: In the READ operations, we use populate() to fill in author details, demonstrating how to work with related collections.
Partial updates: In the UPDATE operation, we only modify fields that are provided in the request, showing how to handle partial updates.
Checking for existence: Before updating or deleting, we first check if the document exists, providing appropriate feedback if it doesn't.
By focusing on these highlights, students can get a clear understanding of how CRUD operations are implemented in a MongoDB with Express application, without getting lost in the details of the full codebase.
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.