Skip to content

mongoose-airlines/recipe-retriever

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Recipe Retriever

This simple application demonstrates basic CRUD functionality using Node, Express, and MongoDB.

One of the best ways to practice putting all the pieces of a MEN-stack app together is by coding a simple example over and over.

Below, you'll find a step-by-step walkthrough for writing the application from start to finish. Here are a few tips to help maximize takeaways from each repetition:

- Type out the functions by hand instead of copying and pasting them.

- Test the functionality of your code at every available opportunity and debug if something isn't working properly. If you wait to debug your code until you've written half of it, you'll find it much more difficult to track down errors.

- Once you've been through the code a time or two, switch to this guide which will provide instructions, but less detail.

- Once you're even more comfortable, try using this guide which will provide instructions, but zero code.



Step-By-Step Instructions (with full code):


Step 1: Navigate to the parent directory where you want to create your app. Use the express generator to create your app's skeleton:

npx express-generator -e recipe-retriever


Step 2: Navigate into the directory and open in VS Code:

cd recipe-retriever
code .


Step 3: Open a terminal in VS Code. Change the name of app.js to server.js:

mv app.js server.js


Step 4: Adjust the /bin/www file to reflect those changes so that your server will start properly:

// Change var app = require('../app'); to:
var app = require('../server');


Step 5: Create directories for the model, controller, database (config), and views, then add the corresponding files within each:

mkdir config models controllers views/recipes
touch models/recipe.js controllers/recipes.js config/database.js 


Step 6: Install Node modules and mongoose using npm:

npm install
npm install mongoose


Step 7: Split the terminal at the bottom of VS Code to open a second window for monitoring the server. Start the server using nodemon and test it out:

nodemon

When you browse to 'localhost:3000' you should see the generic express template.


Step 8: Configure the database connection in database.js:

const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/recipes',
    {useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true}
);

const db = mongoose.connection;
 
db.on('connected', function() {
    console.log(`Connected to MongoDB at ${db.host}:${db.port}`);
});


Step 9: Require the database in server.js (put it near the top!):

require('./config/database');


Step 10: Define the schema in the model (recipe.js):

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var recipeSchema = new Schema({
    title: {type: String, required: true},
    calories: {type: Number},
    mealType: {type: String, enum: ['Snack', 'Breakfast', 'Lunch', 'Dinner', 'Dessert']},
    recipeUrl: {type: String},
    ingredients: [String]
}, {
    timestamps: true
   }
);

module.exports = mongoose.model('Recipe', recipeSchema);


Step 11: Use the terminal to rename users.js --> recipes.js:

mv routes/users.js routes/recipes.js


Step 12: Adjust server.js to reflect the changes from the previous step:

// Change var userRouter = require('./routes/user'); to:
var recipesRouter = require('./routes/recipes');
// Change app.use('/user', userRouter); to:
app.use('/recipes', recipesRouter);


Step 13: Define a route for the index page in routes/recipes.js (replace the existing code):

router.get('/', recipesCtrl.index);


Step 14: Add the corresponding controller in controllers/recipes.js:

function index(req, res){
    Recipe.find({})
    .then((recipes) => {
        res.render('recipes/index', { recipes })
    })
}


Step 15: Use the terminal to create an index view:

touch views/recipes/index.ejs


Step 16: Add a button to add, along with a simple table using ejs in the newly created index.ejs:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <title>Recipe Retriever</title>
</head>
<body>
    <h2>Recipe List</h2><br>
    <table id="recipeList">
        <thead>
            <tr>
                <th>Recipe</th>
                <th>Meal<br>Type</th>
                <th>Recipe<br>URL</th>
            </tr>
        </thead>
        <tbody>
            <% recipes.forEach(function(recipe) { %>
                <tr>
                    <td><%= recipe.title %></td>
                    <td><%= recipe.mealType %></td>
                    <td><a href="http://<%= recipe.recipeUrl %>">See Recipe</a></td>
                    <td><a href="/recipes/<%= recipe._id %>">Details</a></td>
                </tr>
            <% }) %>
        </tbody>
    </table>
    <a href="/recipes/new">Add Recipe</a>
</body>
</html>


Step 17: Add some CSS to make it look a little nicer:

table thead th {
  padding: 5px;
  border-bottom: 2px solid #424748;
}

table td {
  padding: 10px;
  text-align: center;
}

#recipeList td:nth-child(2), #recipeList td:nth-child(3){
  min-width: 100px;
}


Step 18: Change the default 'localhost:3000' landing page to redirect to 'localhost:3000/recipes'. Do this by changing the route in routes/index.js:

router.get('/', function(req, res) {
  res.redirect('/recipes')
});


Step 19: Configure your router in recipes.js and define a route to create a new recipe:

var express = require('express');
var router = express.Router();
var recipesCtrl = require('../controllers/recipes');

router.get('/new', recipesCtrl.new);

module.exports = router;


Step 20: Add the controller in controllers/recipes.js:

var Recipe = require('../models/recipe');

module.exports = {
    new: newRecipe
}

function newRecipe(req, res) {
    res.render('recipe/new');
}


Step 21: Create a view page for adding a recipe:

touch views/recipes/new.ejs


Step 22: Create a form within new.ejs for the user to add an item:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <title>Recipe Retriever</title>
</head>
<body>
    <h2>Enter new recipe:</h2><br>
    <form action="/recipes" method="POST">
        <label>Recipe Name:
            <input type="text" name="title">
        </label><br><br>
        <label>Meal Type (select one):
            <select name="mealType">
                <option value="Snack">Snack</option>
                <option value="Breakfast">Breakfast</option>
                <option value="Lunch">Lunch</option>
                <option value="Dinner">Dinner</option>
                <option value="Dessert">Dessert</option>
            </select>
        </label><br><br>
        <label>Calories (per serving):
            <input type="text" name="calories">
        </label><br><br>
        <label>Ingredients (separate each with a comma):
            <textarea id="ingredientBox" rows="1" type="text" name="ingredients"></textarea>
            </label><br><br>
        <label>Link to recipe:
            <input id="urlInput" type="text" name="recipeUrl">
        </label><br><br>
        <button type="submit" class="btn btn-success">Add</button>
    </form>
</body>
</html>


Step 23: Add minimal CSS in public/stylesheets/style.css:

#ingredientBox {
  width: 250px;
}

#ingredientBox:focus {
  height: 100px;
}

#urlInput {
  width: 430px;
}


Step 24: Define the POST route in routes/recipes.js:

router.post('/', recipesCtrl.create);


Step 25: Create a controller for the route in controllers/recipes.js:

module.exports = {
    new: newRecipe,
    create
}
...
...
...

function create(req, res) {
    req.body.ingredients = req.body.ingredients.replace(/\s*,\s*/g, ',');
    if (req.body.ingredients) req.body.ingredients = req.body.ingredients.split(',');
    Recipe.create(req.body)
    .then((recipe) => {
        console.log('Added recipe to database:', recipe);
        res.redirect('/recipes')
    })
}

Step 26: Navigate to the new recipe page, fill out the fields, and hit the 'Add' button. Check to make sure the POST request shows up in the terminal currently running the server:

new-post


Step 27: Add a route for the 'Details' button that was just created. Add the following to routes/recipes.js:

router.get('/:id', recipesCtrl.show);


Step 28: Add the controller for the new route in controllers/recipes.js:

function show(req, res) {
    Recipe.findById(req.params.id)
    .then((recipe) => {
        res.render('recipes/show', { recipe })
    })
}


Step 29: Using the terminal, create a file called show.ejs:

touch views/recipes/show.ejs


Step 30: Write the HTML/ejs to display the data for an individual item in show.ejs:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <title>Recipe Retriever</title>
</head>
<body>
    <h2>Recipe Details</h2><br>
    <div class="details-bold">Recipe Name:</div>
    <div><%= recipe.title %></div>
    <div class="details-bold">Meal Type:</div>
    <div><%= recipe.mealType %></div>
    <div class="details-bold">Calories:</div>
    <div><%= recipe.calories %></div>
    <div class="details-bold">Ingredients:</div>
    <%= recipe.ingredients.map(i => i).join(', ') %>
    <div class="details-bold">Recipe Link:</div>
    <div><%= recipe.recipeUrl %></div>
    <a href="/recipes">Back to Recipes</a>
</body>
</html>


Step 31: Add some CSS to clean up the display:

.details-bold {
  font-weight: bold;
  text-decoration: underline;
}


Step 31.5: Use npm to install the method-override package:

npm i method-override

...then require it in server.js:

let methodOverride = require('method-override');

...and add it to the middleware:

app.use(methodOverride('_method'));


Step 32: Add a button to index.ejs to handle deletion:

<tr>
    <td><%= recipe.title %></td>
    <td><%= recipe.mealType %></td>
    <td><a href="http://<%= recipe.recipeUrl %>">See Recipe</a></td>
    <td><a href="/recipes/<%= recipe._id %>">Details</a></td>
    <!-- Add the following form here: -->
    <form action="/recipes/<%= recipe._id %>?_method=DELETE" method="POST">
    <td><button type="submit" class="btn btn-danger">X</button></td>
    </form>
</tr>


Step 33: Add the route to handle deletions in routes/recipes.js:

router.delete('/:id', recipesCtrl.delete);


Step 34: Add the corresponding controller to controllers/recipes.js (don't forget to add it to module.exports!):

module.exports = {
    new: newRecipe,
    create,
    index,
    show,
    // Add this line:
    delete: deleteRecipe
}

function deleteRecipe(req, res) {
    Recipe.findByIdAndDelete(req.params.id)
    .then(res.redirect('/recipes'))
}


Step 35: Add a button in show.ejs to handle updating an item:

<!-- ... -->
<div class="details-bold">Recipe Link:</div>
    <div><%= recipe.recipeUrl %></div>
    <!-- Add the button here: -->
    <form action="/recipes/<%= recipe._id %>/edit">
        <button type="submit" class="btn btn-warning">Edit</button>
    </form>
    <a href="/recipes">Back to Recipes</a>
</body>


Step 36: Add the route to handle showing the update page in routes/recipes.js:

router.get('/:id/edit', recipesCtrl.edit);


Step 37: Add the corresponding controller in controllers/cuisine.js:

module.exports = {
    new: newRecipe,
    create,
    index,
    show,
    delete: deleteRecipe,
    // Add this line:
    edit
}

function edit(req, res) {
    Recipe.findById(req.params.id)
    .then((recipe) => {
        res.render('recipes/edit', { recipe })
    })
}


Step 38: Using the terminal, create a new view called edit.ejs:

touch views/recipes/edit.ejs


Step 39: Copy the form over from show.ejs to edit.ejs, but modify it to auto-populate the values of each field with the current record's info:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <title>Recipe Retriever</title>
</head>
<body>
    <h2>Edit Recipe:</h2><br>
    <form action="/recipes/<%= recipe._id %>?_method=PUT" method="POST">
        <label>Recipe Name:
            <input type="text" name="title" value="<%= recipe.title %>">
        </label><br><br>
        <label>Meal Type (select one):
            <select name="mealType">
                <option selected ><%= recipe.mealType %></option>
                <option value="Snack">Snack</option>
                <option value="Breakfast">Breakfast</option>
                <option value="Lunch">Lunch</option>
                <option value="Dinner">Dinner</option>
                <option value="Dessert">Dessert</option>
            </select>
        </label><br><br>
        <label>Calories (per serving):
            <input type="text" name="calories" value="<%= recipe.calories %>">
        </label><br><br>
        <label>Ingredients (separate each with a comma):
            <textarea id="ingredientBox" rows="1" type="text" name="ingredients"><%= recipe.ingredients.map(i => i).join(', ') %></textarea>
            </label><br><br>
        <label>Link to recipe:
            <input id="urlInput" type="text" name="recipeUrl" value="<%= recipe.recipeUrl %>">
        </label><br><br>
        <button type="submit" class="btn btn-success">Update</button>
    </form>
</body>
</html>


Step 40: Add the POST route to send the record to be updated in routes/recipes.js:

router.put('/:id', recipesCtrl.update);


Step 41: Add the corresponding controller in controllers/recipes.js:

function update(req, res) {
    req.body.ingredients = req.body.ingredients.replace(/\s*,\s*/g, ',');
    if (req.body.ingredients) req.body.ingredients = req.body.ingredients.split(',');
    Recipe.findByIdAndUpdate(req.params.id, req.body, {new: true})
    .then((recipe) => {res.redirect(`/recipes/${recipe._id}`)})
}


Step 42: Profit.

About

Code for practicing basic MEN-stack creation

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published