Book List CRUD App with Mongoose - Create & Read




Lesson Objectives

  1. Review the flow of technology
  2. Initialize a directory
  3. Start express
  4. Create New Route
  5. Create Create Route
  6. Connect Express to Mongo
  7. Create Book Model
  8. Have Create Route Create data in MongoDB
  9. Create Index Route
  10. Have Index Route Render All Books
  11. Have Create Route redirect to Index After Book Creation
  12. Create Show Route
  13. Have Index Page Link to Show Route
  14. Create show.ejs



Review the flow of technology

Initialize a directory

  1. mkdir booklist
  2. cd booklist
  3. touch server.js
  4. code .
  5. npm init -y
  6. npm install express dotenv
  7. touch .env

Remember to add a .gitignore with node_modules if you don't have a global .gitignore.




Add The PORT Variable to .env

PORT=3000



Create Your Basic Express Server

// Dependencies
const express = require('express');
const app = express();
require('dotenv').config();

// Listener
const PORT = process.env.PORT;
app.listen(PORT, () => console.log(`server is listning on port: ${PORT}`));

Stop! Check your work!




Connect Express to MongoDB Atlas

  1. npm install mongoose
  2. Navigate to MongoDB.com and create a new database or get prepapred to use an old one.
  3. Add your connection string to your .env file (do not copy and paste this one) then update the sub-database to use booklist like we've done in the connection string example below



Inside .env

DATABASE_URL=mongodb+srv://<username>:<password>@general-assembly.1wjse.mongodb.net/booklist?retryWrites=true&w=majority

Inside server.js, connect to your database

// Dependencies
const mongoose = require('mongoose');

// Database Connection
mongoose.connect(process.env.DATABASE_URL, {
	useNewUrlParser: true,
	useUnifiedTopology: true,
	useFindAndModify: false,
	useCreateIndex: true,
});

// Database Connection Error/Success
// Define callback functions for various events
const db = mongoose.connection
db.on('error', (err) => console.log(err.message + ' is mongo not running?'));
db.on('connected', () => console.log('mongo connected'));
db.on('disconnected', () => console.log('mongo disconnected'));

Stop! Check your work!




Create Create Route

// Create
app.post('/books', (req, res) => {
	res.send('received');
});

Stop! Check your work with Postman.




Set Up Your Body Parser Middleware

// Middleware
// Body parser middleware: give us access to req.body
app.use(express.urlencoded({ extended: true }));
app.post('/books', (req, res) => {
	res.send(req.body);
});

Stop! Check your work with Postman

Let's add:

title: Cracking the Coding Interview
author: Gayle Laakmann McDowell




Format Checkbox Data Properly

In server.js:

app.post('/books', (req, res) => {
	if (req.body.completed === 'on') {
		//if checked, req.body.completed is set to 'on'
		req.body.completed = true;
	} else {
		//if not checked, req.body.completed is undefined
		req.body.completed = false;
	}
	res.send(req.body);
});

Stop! Check your work again by resending the previous request.




Create Books Model

  1. mkdir models
  2. touch models/book.js
  3. Create the book schema

In models/book.js:

const mongoose = require('mongoose');

const bookSchema = new mongoose.Schema({
	title: { type: String, required: true },
	author: { type: String, required: true },
	completed: Boolean,
});

const Book = mongoose.model('Book', bookSchema);

module.exports = Book;



Have Create Route Create data in MongoDB

Inside server.js:

// Dependencies
const Book = require('./models/book.js');

// Routes / Controllers
// Create
app.post('/books', (req, res) => {
	if (req.body.completed === 'on') {
		//if checked, req.body.completed is set to 'on'
		req.body.completed = true;
	} else {
		//if not checked, req.body.completed is undefined
		req.body.completed = false;
	}

	Book.create(req.body, (error, createdBook) => {
		res.send(createdBook);
	});
});

Stop! Check your work with Postman.




Create The New Page Route

Remember INDUCES (index, new, delete, update, create, edit, show) to help organize your routes and avoid any conflicts.

// Routes / Controllers
// New
app.get('/books/new', (req, res) => {
	res.send('new');
});



Create The New View

  1. npm install ejs
  2. mkdir views
  3. touch views/new.ejs

In views/new.ejs:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Book List</title>
	</head>
	<body>
		<h1>New Book Page</h1>
		<form action="/books" method="POST">
			Title: <input type="text" name="title" /><br />
			Author: <input type="text" name="author" /><br />
			Completed: <input type="checkbox" name="completed" /><br />
			<input type="submit" name="" value="Add Book" />
		</form>
	</body>
	</body>
</html>



Render the view

// Routes / Controllers
// New
app.get('/books/new', (req, res) => {
	res.render('new.ejs');
});

Stop! Check your work! Is the view working? We'll check the form functionality soon!




Create The Index Route

Remember INDUCES

In server.js:

// Index
app.get('/books', (req, res) => {
	res.send('index');
});

Stop! Check your work.




Create The Index View

touch views/index.ejs

In views/index.ejs:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Book List</title>
	</head>
	<body>
		<h1>My Reading List</h1>
	</body>
</html>



Render The Index View

// Index
app.get('/books', (req, res) => {
	res.render('index.ejs');
});



Have Index Route Pass Book Data to the View

// Index
app.get('/books', (req, res) => {
	Book.find({}, (error, allBooks) => {
		res.render('index.ejs', {
			books: allBooks,
		});
	});
});

We could just res.send allBooks and check with Postman, but we're getting confident and bold! Let's stick with a render for now and check it in a moment.




Update the Index View to Display All Books:

In views/index.ejs:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title></title>
	</head>
	<body>
		<h1>Books index page</h1>
		<ul>
			<% books.forEach(book => { %>
			<li><%=book.title; %></li>
			<% }) %>
		</ul>
	</body>
</html>

We could use any type of loop we want, but the forEach loop reads really nicely!




In views/index.ejs:

<nav>
	<a href="/books/new">Add a New Book</a>
</nav>



Update Your Create Route redirect to Index After Book Creation

In server.js:

Book.create(req.body, (error, createdBook) => {
	res.redirect('/books');
});

Stop! Check your work. Navigate to your form.

Let's add:

title: You don't know JS Yet
author: Kyle Simpson

We should be redirected to /books and see our new book there. Success? Awesome! Error? Take a moment to fix it.




In views/index.ejs:

<li>
	<a href="/books/<%=book._id; %>"> <%=book.title; %> </a>
</li>

Notice how we have to put an underscore before id. That's a MongoDB convention.

Stop! Check your work. Click one of the links and make sure it redirects you to the right place.




Create Show Route

Remember INDUCES

// Show
app.get('/books/:id', (req, res) => {
	Book.findById(req.params.id, (err, foundBook) => {
		res.send(foundBook);
	});
});

Stop! Check your work.




Create A Show Page

touch views/show.ejs

In views/show.ejs:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<nav>
			<a href="/books">Back to Books Index</a>
		</nav>
		<h1><%=book.title %></h1>
		Author: <%= book.author%>
		<br />
		Completed: <%= book.completed %>
	</body>
</html>



Render the Show Page

// Show
app.get('/books/:id', (req, res) => {
	Book.findById(req.params.id, (err, foundBook) => {
		res.render('show.ejs', {
			book: foundBook,
		});
	});
});

Final stop! Check your work :)