No description, website, or topics provided.
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
data
solution-code-01
solution-code-02
starter-code
.gitignore
README.md

README.md

ck Logo

Vue.js & Express API | MyRecipeBook

Introduction

Cooking is hard, but you know what's harder? Remembering recipes. Remember that one time you made that amazing dish and forgot what ingredients go into it? It's a terrible feeling.

In this exercise, we'll make a an application to keep track of your favorite recipes so you never have this disappointing experience again.

This application can apply to food or dishes, so feel free to alter the language accordingly.

Instructions

You are going to make from scratch a web application recipe book with two parts: the front-end in Vue.js (Client) and the back-end in Express (Server).

Client

The users that go on your websites will have access to 4 possible routes:

  • /: Redirect the user to /dishes
  • /dishes: Display a list of all dishes, with a link
  • /dishes/:dishId: Display the recipe of a specific dish, with the descriptions and all it's ingredients
  • /new-dish: Display a form page to add a new dish

Example of page seen on "/dishes" /dishes

Example of page seen on "/dishes/598c147d82ff710a38fd6027" /dishes/:dishId

Server

You are going to create your own Rest API with Express. For that, 2 models are going to be needed: Ingredient and Dish

// server/models/ingredient.js
// ...
const IngredientSchema = new Schema({
  name: {
    type: String,
    required: true
  },
  unity: String
});
module.exports = mongoose.model('Ingredient', IngredientSchema);
// server/models/dish.js
// ...
const DishSchema = new Schema({
  name: {
    type: String,
    required: [true, 'name is required']
  },
  description: {
    type: String,
    required: [true, 'description is required']
  },
  image: String,
  ingredients: [
    {
      ingredient: {
        type: Schema.Types.ObjectId,
        ref: 'Ingredient'
      },
      quantity: Number,
      _id: false
    }
  ]
});
module.exports = mongoose.model('Dish', DishSchema);

For your API, you can create the following endpoints:

  • GET /api/dishes: Returns all dishes with only their id, names and images
  • GET /api/dishes/:dishId: Returns all information on a specific dish
  • PUT /api/dishes/:dishId: Updates a specific dish
  • POST /api/dishes: Creates a new dish

To help you creating the database, there are 2 files with some data: data/dishes.json and data/ingredients.json

Iterations

Iteration 1 | Initialize and populate your database

To start, you will populate your database based on the data folder.

For that you can:

  • Initialize a new Express project inside starter-code/server/ folder
  • Create your models Dish and Ingredient
  • Populate your database with a bin/seeds.js file based on data/dishes.json and data/ingredients.json

Iteration 2 | Creating your first endpoint

Now you will create an endpoint to list all dishes: GET /api/dishes.

At the end of this iteration, you should see the following result when you go on http://localhost:3000/api/dishes:

[
  {
    "_id": "598c147d82ff710a38fd6027",
    "name": "Pizza",
    "image": "https://i.imgur.com/eTmWoAN.png",
    "description": "Pizza is a yeasted flatbread typically topped with tomato sauce and cheese and baked in an oven. It is commonly topped with a selection of meats, vegetables and condimentsc"
  },
  {
    "_id": "598c147d82ff710a38fd6029",
    "name": "Sweet Potato",
    "image": "https://i.imgur.com/hGraGyR.jpg",
    "description": "A salad is a dish consisting of a mixture of small pieces of food, usually featuring vegetables.[1][2] They are typically served at room temperature or chilled, with notable exceptions such as south German potato salad which is served warm. Salads may contain virtually any type of ready-to-eat food."
  },
  //...
]

Iteration 3 | Listing dishes

Having a link to see all dishes in a JSON is a first good step and now we are going to use a Vue application. That's why we are now creating a dishes page (/dishes) that will display all dishes in a list, with their names and images.

In that iteration, we recommand you to:

  • Initialize a Vue application in starter-code/client/ folder
  • Create a component that will be displayed on the following path /dishes
  • Fetch and display the dishes from GET /api/dishes
  • Redirect the home page (/) to /dishes

Iteration 4 | Show one dish details

You now have a list of dishes, it's time to link them to a detail page.

On your list of dishes page, create a link to /dishes/:dishId that displays the name, the image and the description of a specific dish. You should also create an "Edit description" button just after your description.

Iteration 5 | Edit the description of a dish

In this iteration, we are going to edit the description without changing the page!

In term of user experience, the scenario should be the following:

  • The user clicks on the "Edit description" button
  • The text description is changed by a textarea with the description inside it add the button is changed by "Save description"
  • When the user clicks on "Save description", it replaces the textarea by a simple paragraph, the "Edit description" button comes back and the new description must be saved

For that, you will probably need to create a PUT /api/dished/:dishId endpoint.

Iteration 6 | List all ingredients

A dish detail page is nice, but we're missing one important piece: ingredients.

Over the next two iterations, we're going to add functionality to add ingredients to our dishes.

The first step in doing so is going to be displaying a list of possible ingredients on the recipe's page. For that, you will need to:

  • Add an API endpoint GET /api/ingredients that displays all ingredients
  • Use this endpoint to display all your ingredients in your dish detail page (/dishes/:dishId)

In your list of all ingredients, you should display:

  • The ingrendient's name
  • A number input (will be used in the next iteration)
  • The unity
  • A "Add ingredient" button (will be used in the next iteration)

Iteration 7 | Add ingredient to dish

Create a function in the single dish component. When someone clicks the "Add ingredient" button from the previous iteration, it should add the ingredient to the dish and display it.

Do

The request should be done inside of your dish service.

The API endpoint is a POST to '/drinks/:drinkId/ingredients/:id/add'. It also takes in a parameter for quantity in the body, which is a number.

Add a list of a dish's ingredients to the single dish component. Upon successfully adding the ingredient to the dish, display the ingredient in the list.

Iteration 8 | Bonus | Creating New Ingredients & Dishes

Create a new route for adding new dishes. Add a link in the home page to display a form. This form will make a POST request to /dishes with a name, image, and description.

In addition, create a route on the home page to display a form to create a new ingredient. The route is a POST to /ingredients, and takes a name, image, and description in the body.

Solution

You will find the solution inside the folder "solution-code-XX" where "XX" is the number of the iteration.

Iteration 1 | Solution | Populate your database

You have two ways to populate your databse:

  1. Use mongoinsert to import your documents:
    • $ mongoimport -d lab-vue-express-recipes -c dishes --file data/dishes.json --jsonArray
    • $ mongoimport -d lab-vue-express-recipes -c ingredients --file data/ingredients.json --jsonArray
  2. Use a seed file

The second option takes more time but this is what we are going to see. We will also start to create the express project with our Dish and Ingredient models.

You should do these commands:

$ git clone https://github.com/ta-web-paris/lab-vuejs-express-recipes.git
$ cd lab-vuejs-express-recipes
$ cd starter-code
$ express --git server
destination is not empty, continue? [y/N] y

The last command is creating an Express project inside the "server" folder (that already exists but that is empty) and add a ".gitignore" file (--git).

You can add the dev script inside your "server/package.json"

"scripts": {
  "start": "node ./bin/www",
  "dev": "nodemon ./bin/www"
}

So now, you can install your dependencies and run your project with Nodemon:

$ cd server
$ npm install
$ npm run dev

Now, your project should run on port 3000 (the port is defined in "server/bin/www"). You can check it by going on http://localhost:3000.

We are now ready to create our models Dish and Ingredient inside starter-code/server/models/.

// ----- server/models/ingredient.js ----- 
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const IngredientSchema = new Schema({
  name: {
    type: String,
    required: true
  },
  unity: String
});
module.exports = mongoose.model('Ingredient', IngredientSchema);
// ----- server/models/dish.js ----- 
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const DishSchema = new Schema({
  name: {
    type: String,
    required: [true, 'name is required']
  },
  description: {
    type: String,
    required: [true, 'description is required']
  },
  image: String,
  ingredients: [
    {
      ingredient: {
        type: Schema.Types.ObjectId,
        ref: 'Ingredient'
      },
      quantity: Number,
      _id: false
    }
  ]
});
module.exports = mongoose.model('Dish', DishSchema);
// ----- server/bin/seeds.js ----- 
const mongoose = require('mongoose');
const Ingredient = require('../models/ingredient.js');
const Dish = require('../models/dish.js');
const dataIngredients = require('../../../data/ingredients.json');
const dataDishes = require('../../../data/dishes.json');

mongoose.connect('mongodb://localhost/lab-vue-express-recipes', {useMongoClient: true});

// 1st: remove all ingredients
Ingredient.remove({}, function(err) { 
  if (err) {
    throw err;
  }
  console.log("All ingredients are removed");
  // 2nd: remove all dishes
  Dish.remove({}, function(err) {
    if (err) {
      throw err;
    }
    console.log("All dished are removed");
    
    // 3rd: add all ingredients
    Ingredient.create(dataIngredients, (err, ingredients) => {
      if (err) {
        throw err;
      }
      ingredients.forEach((ingredient) => {
        console.log("New ingredient:", ingredient.name)
      });

      // 4th: add all dishes
      Dish.create(dataDishes, (err, dishes) => {
        if (err) {
          throw err;
        }
      
        dishes.forEach((dish) => {
          console.log("New dish:", dish.name)
        });
        mongoose.connection.close();
      });
    });
  });
});

You can also update your server/package.json to add a script to run the seeds.

  "scripts": {
    "start": "node ./bin/www",
    "dev": "nodemon ./bin/www",
    "seeds": "node ./bin/seeds.js"
  },

Don't forget to import mongoose with NPM:

$ npm install mongoose --save

Now you should only run $ npm run seeds to populate your database :)

Iteration 2 | Solution | Creating your first endpoint

Before creating an endpoint to list all dishes (GET /api/dishes), we will start with a more basic one (GET /) that only renders { "text": "Hello world!" }.

At the moment, you have a regular Express application that displays some views. We are going to remove all the views and send JSON for our REST API.

First, remove "server/public/", "server/views/" and "server/routes/users.js".

Then, in "server/app.js", remove the following lines that we don't need anymore:

var favicon = require('serve-favicon');
var users = require('./routes/users');
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use('/users', users);

After that, remove all the res.render you can find (that tries to render some views). There is one at the end of "server/app.js" and in "server/routes/index.js". Change them with the following code:

// ----- server/app.js ----- 
// error handler
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.json({
    message: err.message,
    error: req.app.get('env') === 'development' ? err : {}
  });
});
// ----- server/routes/index.js ----- 
/* GET home page. */
router.get('/', function(req, res, next) {
  res.json({ text: "Hello world!" });
});

Now, if you go to http://localhost:3000 or http://localhost:3000/wrongUrl, you should see the following JSON

{
  "text": "Hello world!"
}
{
  "message": "Not Found",
  "error": {
    "status": 404
  }
}

Now we have a working basic REST API that handle the request GET /!

Let's create an endpoint GET /api/dishes. We need to connect with mongoose in our server/app.js and we will need to add a new route in server/routes/index.js.

// ----- server/app.js ----- 
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/lab-vue-express-recipes', {useMongoClient: true});
// ...
// ----- server/routes/index.js ----- 
// ...
/* GET all dishes */
router.get('/api/dishes', function(req, res, next) {
  Dish.find({}).exec((err, dishes) => {
    if (err) {
      next(err);
    }
    res.json(dishes); 
  })
});

Now if you go to http://localhost:3000/api/dishes, you will see your list of dishes!

Iteration 3 | Solution | Listing dishes

WIP

Iteration 4 | Solution | Show one dish details

WIP

Iteration 5 | Solution | Edit the description of a dish

WIP

Iteration 6 | Solution | List all ingredients

WIP

Iteration 7 | Solution | Add ingredient to dish

WIP