DEV Community

Cover image for Beginner Friendly Project Recipe APP ( NodeJs & PostGreSql)
Tahzib Mahmud Rifat
Tahzib Mahmud Rifat

Posted on

Beginner Friendly Project Recipe APP ( NodeJs & PostGreSql)

3/14/24

Introduction

We are going to to build a website where we can see our recipes and how to cook them with the directions of the hotel. We will store our data using postgresql and control the backend using nodejs. We will implement full CRUD operation here.

PGAdmin

Creating Roles

  1. Install postgresql. Open pgAdmin.
  2. login with password.
  3. Go to Login/Group Roles, right click on that and select create

  4. In General give a name like: projectRecipe

  5. Then go to Definition and give the password.

  6. Now go to privileges and give permission to

    • Can login
    • Superuser
    • Create roles
    • Create databases
    • Inherit rights from the parent roles
  7. And click save.

Creating new Database

  1. Go to Databases and right click on it. Select Create and then Select Database.

  2. Now in general, give database name; we gave recipebookdb and select the Owner that we recently created projectRecipe and save it. This will create the database.

Creating Table

  1. In recipebookdb we can see schemas and there we will have public and then tables. We are going to create a new table now.

  2. Now a new tab will pop up. Where we will see Create-Table. There in general we give the table Name as recipes and the Owner will be, what we newly created projectRecipe and then save it. It will create a new table.

  3. Now we are going to create column by clicking on tables -> columns -> create -> column.

  4. In general the name of the column is id. And for the data type we go to Definition and select Data type serial and press save.

Setting id as PRIMARY KEY

  1. To set the id as primary key at first we go to constraints and then create and select Primary Key.

Now we set the primary key name as pri_key from general.

next we go to definition and select the id column.

In SQL we can see the query.

  1. Now we are going to create another column called name.

same way we create ingredient, update: changed it to, ingredients ** which data type is text. and **directions column also.

             #Database is done now
Enter fullscreen mode Exit fullscreen mode

3/15/24

Setting Up environment

  1. Create a folder name recipebook and open it via vs code.

  2. Open the terminal and type, npm init

  • Give description name as Recipe Manager
  • entry point : app.js
  • author : Tahzib Mahmud Rifat,
  • type yes .

that's it.


4 . Adding dependencies on package.json file.

"dependencies": {
"body-parser": "*",
"consolidate": "*",
"dust": "*",
"dustjs-helpers": "*",
"dustjs-linkedin" : "*",
"express" : "*",
"pg": "*"
}

The dependencies section in a package.json file lists the packages that your project depends on. Each dependency entry consists of a package name and a version range specifier. In the example you provided, the version range specifier is "*", which means it will match any version of the package. All the dependencies have their own meaning, you can search it if you want.

note that using "" as a version specifier can lead to potential issues in the future when newer versions of the dependencies introduce breaking changes or compatibility issues with code.

5 .
If you run npm install in your project directory, npm will read the package.json file to determine the dependencies listed under the "dependencies" section. It will then proceed to download and install those dependencies, along with their transitive dependencies if any.

In your case, npm will install the following packages:

body-parser
consolidate
dust
dustjs-helpers
dustjs-linkedin
express
pg
Since you've used "" as the version specifier for each dependency, npm will install the latest version available that matches the version range specified. If you haven't already installed these packages, npm will create a node_modules directory in your project and install the packages there. It will also generate or update the **package-lock.json* file to reflect the exact versions of the dependencies installed.

After running npm install, your project should have all the necessary dependencies installed and ready to use.


3/16/24

app.js

  1. Call all the dependencies
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const cons = require('consolidate');
const dust = require('dustjs-helpers');
const pg = require('pg');
const app = express();
Enter fullscreen mode Exit fullscreen mode
  1. after that add this,
// DB Connect String
const connect = 'postgres://projectRecipe:R5f@t87D@localhost:5432/recipebookdb';

// Assign Dust Engine to .dust files
app.engine('dust', cons.dust);

// Set default Ext .dust
app.set('view engine', 'dust');
app.set('views', __dirname + '/views');

// Set Public Folder
app.use(express.static(path.join(__dirname, 'public')));

// Body parser Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false}));

app.get('/', (req, res) => {
    console.log('Testing');
})

// Server
app.listen(3000, () =>{
    console.log('Server Started On Port 3000');
});
Enter fullscreen mode Exit fullscreen mode
  1. Now, if we run node app on terminal we will see and it means the server is started,

Loading DUST template

Dust is a powerful templating engine for JavaScript.

  1. At first we are going to create some folders and files, Folders: public, css, js, views. Files: style.css , index.dust, layout.dust.

  2. In index.dust type,

           This is our index dust file.
    
  3. Let's go to app.js and in route part add
    res.render('index);
    And now if you have you server started on port 3000 then go to your browser and search localhost:3000 and it will show

  4. So we have layout.dust and we are going to modify it as our will because it our frontend part. So, the layout.dust

<!DOCTYPE html>
<html>
<head>
    <title>RecipeBook</title>
</head>
<body>
    <header>
        <h1>Recipe App</h1>
        <nav>
            <ul>
                <li><a href="/">Home</a></li>
                <li><a href="/recipes">Recipes</a></li>
                <li><a href="/about">About</a></li>
            </ul>
        </nav>
    </header>

    <main>
        {+body /} // this is the dust syntax 
    </main>

    <footer>
        &copy; 2022 Recipe App. All rights reserved.
    </footer>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

and in index.dust we are going to add

{>"layout" /}

{<body}
THis is our index dust file.
{/body}
Enter fullscreen mode Exit fullscreen mode

Now, go to your browser and search localhost:3000 and it will show the update.

Next well see some postgresql, bootsrap and all.


3/17/24

Installing Bootstrap and Fetching data from Postgres

  1. Download bootstrap zip file from the link or just search download bootstrap. https://getbootstrap.com/docs/4.0/getting-started/download/

  1. Now we copy the bootstrap.css from the downloaded file and paste it to out project public->css folder.

same way we will copy the bootstrap.js file to our public->js folder.

  1. Let's go to layout.dust now. And we will simply link the bootstrap.css, style.css and bootstrap.js files.

  2. Adding jquery link to layout.dust.
    <script src="https://code.jquery.com/jquery-3.7.1.js" integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4=" crossorigin="anonymous"></script>

  1. Now, if we search localhost:3000, it will show us the modified fonts and version which means bootstrap has been implemented.

  2. Now we are going to add some html.

<div class="container">
            <div class="row">
                <div class = "col-md-offset-2 col-md-7">
                    {+body /}
                </div>
            </div>
        </div>
Enter fullscreen mode Exit fullscreen mode

  1. Now we are going to modify our style.css.

  2. Adding some data to our database using pgadmin. Go to recipe table and click on that All Rows.

Next go to Data Output and add your data. Do not add id because it will increase automatically.


3/18/24

Rendering The index View

  1. We are going to update the app.get in app.js.
app.get('/', (req, res) => {
    // PG Connect
    pg.connect(connect, function(err, client, done) {
        if(err){
            return console.error('Error fetching client from pool', err);
        }
        client.query('SELECT*FROM recipes', function(err,result){

            if(err){
                return console.error('Error runing query', err);
            }
            res.render('index', {recipes: result.rows});
            done();
        });
    });
});
Enter fullscreen mode Exit fullscreen mode

Error


3/19/24

So the previous days problem was solved. The error we are encountering indicates that pg.connect is not a function. This suggests that the pg module we have imported does not have a connect method. In newer versions of the pg module, pg.connect has been deprecated, and we should use the Pool constructor instead.

So here is the updated code:

const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const cons = require('consolidate');
const dust = require('dustjs-helpers');
const { Pool } = require('pg'); // Import Pool from pg module
const app = express();

// DB Connect String
const connect = 'postgres://projectRecipe:R5f@t87D@localhost:5432/recipebookdb';

// Assign Dust Engine to .dust files
app.engine('dust', cons.dust);

// Set default Ext .dust
app.set('view engine', 'dust');
app.set('views', __dirname + '/views');

// Set Public Folder
app.use(express.static(path.join(__dirname, 'public')));

// Body parser Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

// Route(url)
app.get('/', (req, res) => {
    // Create a new pool
    const pool = new Pool({
        connectionString: connect
    });

    // Use the pool to execute query
    pool.query('SELECT * FROM recipes', (err, result) => {
        if (err) {
            console.error('Error running query', err);
            res.status(500).send('Error retrieving data');
        } else {
            res.render('index', { recipes: result.rows });
        }

        // Release the client from the pool
        pool.end();
    });
});

// Server
app.listen(3000, () => {
    console.log('Server Started On Port 3000');
});

Enter fullscreen mode Exit fullscreen mode
  1. Now let's modify on index.dust. Now we add collapse in our view button using bootstrap javascrip. So, at first we go to bootstrap documentation and go to, components->collapse. Link of bootstrap doc:Link of BootStrap Doc

and copy these portion,
data-toggle="collapse" href="#recipe_{id}" role="button" aria-expanded="false" aria-controls="recipe_{id}"

paste it to after class,

  1. Now add a new div for collapse data after h4,

<div class = "collapse" id="recipe_{id}">
TEST
</div>

and added ingredients, directions and edit and remove button. Here is the full layout code after editing.

{>"layout" /}

{<body}
    {#recipes}
        <div class = "well">
            <h4>{name} 
            <button class = "btn btn-default pull-right" data-toggle="collapse" href="#recipe_{id}" role="button" aria-expanded="false" aria-controls="recipe_{id}">
            <span class="glyphicon glyphicon-triangle-bottom" aria-hidden="true"></button></h4>

            <div class = "collapse" id="recipe_{id}">
                <br />
                <br />
                <p>
                    <strong>Ingredients: </strong>{ingredients}
                </p>
                <p>
                    <strong>Directions: </strong>{directions}
                </p>
                <br/>
                <hr/>
                <button class="btn btn-default edit-recipe">
                    <span class="glyphicon glyphicon-edit" aria-hidden="true">
                </button>

                <button class="btn btn-danger delete-recipe">
                    <span class="glyphicon glyphicon-remove" aria-hidden="true">
                </button>
            </div>
        </div>
    {/recipes}
{/body}
Enter fullscreen mode Exit fullscreen mode

and our website,


3/20/24

Add recipes to the application

  1. Adding the Add btn, we just type these,
<button type="button" class = "btn btn-primary btn-block" data-toggle="modal" data-target="#formModal">
        Add Recipe
    </button>
    <br/>
Enter fullscreen mode Exit fullscreen mode

So, this button will trigger Modal. To add the modal we have to add this code, i am going to add this after {/recipes},

<!-- Add Form Modal -->
    <div class="modal fade" id="formModal" tabindex="-1" role="dialog" aria-labelledby="formModalLabel"
        aria-hidden="true">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <form action="/add" method="post">
                    <div class="modal-header">
                        <h5 class="modal-title" id="exampleModalLabel">Add Recipe</h5>
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                            <span aria-hidden="true">&times;</span>
                        </button>
                    </div>
                    <div class="modal-body">
                        <div class="form-group">
                            <label for="">Ingredients</label>
                            <textarea class="form-control" name="ingreients"></textarea>
                        </div>
                        <div class="form-group">
                            <label for="">Directions</label>
                            <textarea class="form-control" name="directions"></textarea>
                        </div>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                        <input type="submit" class="btn btn-primary" value="Save"/>
                    </div>
                </form>
            </div>
        </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

This will give us,

And after clicking on save btn we will get /add.


3/21/24

  1. Now we are going to create app.post for getting the /add ,
app.post('/add', (req, res) => {
    //create a new poos again
    const pool = new Pool({
        connectionString: connect
    });

    // use the pool to execute query
    pool.query('INSERT INTO recipes(name, ingredients, directions) VALUES($1, $2, $3)', [req.body.name, req.body.ingredients, req.body.directions], (err, result) =>{
        if(err){
            console.error("Error running query", err);
            res.status(500).send("error retrieving data");
        }
        else{
            res.redirect('/');
        }
        //release the client from the pool
        pool.end(); 
    });
});
Enter fullscreen mode Exit fullscreen mode

3/22/24

Delete recipe

  1. To delete a recipe we need to pass a id, so we need data attribute. No we add data-id="{id}" in out delete button.

  2. create main.js file in public->Js folder.

$(document).ready(function(){
    $('.delete-recipe').on('click', function(){
        let id = $(this).data('id');
        let url = '/delete/'+id;

        if(confirm('Delete Recipe?')){
            $.ajax({
                url: url,
                type: 'DELETE',
                success: function(result){
                    console.log('Deleteing your Recipessss....');
                    window.location.href='/';
                },
                error: function(err){
                    console.log("There is some issue" + err);
                }
            })
        }
    })
})
Enter fullscreen mode Exit fullscreen mode

Next we are going to add app.delete to our app.js file.

app.delete('/delete/:id', (req, res)=>{
    //create a new poos again
    const pool = new Pool({
        connectionString: connect
    });

    pool.query('DELETE FROM recipes WHERE id = $1', [req.params.id], (err, result) =>{
        if(err){
            console.error("Error running query", err);
            res.status(500).send("error retrieving data");
        }
        else{
            res.send(200);
        }
        //release the client from the pool
        pool.end(); 
    });
});
Enter fullscreen mode Exit fullscreen mode

This will give us our website a delete option on work.

Updating the site

Here comes the difficult task. We have to update our data, it show a modal where we can edit and we have to modify our update button beside the delete button. So lets see which our buttons,

View button

Delete Button

Update Button

  1. So, in our index.dust we edit the update button code,
<!-- update button--> 
            <button class="btn btn-default edit-recipe"
            data-toggle="modal" 
            data-target="#editFormModal"
            data-id="{id}"
            data-name="{name}"
            data-ingredients="{ingredients}"
            data-directions="{directions}"
            >
                <span class="glyphicon glyphicon-edit" aria-hidden="true">
            </button>
Enter fullscreen mode Exit fullscreen mode


2 . Now we add the modal for update,

<!-- Edit Form Modal -->
    <div class="modal fade" id="editFormModal" tabindex="-1" role="dialog" aria-labelledby="editFormModalLabel"
        aria-hidden="true">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <form action="/edit" method="post">
                    <input type="hidden" id="edit-form-id" name="id" />
                    <div class="modal-header">
                        <h5 class="modal-title" id="exampleModalLabel">Add Recipe</h5>
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                            <span aria-hidden="true">&times;</span>
                        </button>
                    </div>
                    <div class="modal-body">
                        <div class="form-group">
                            <label>Recipe Name</label>
                            <input type="text" name="" class="form-control" name="name" id="edit-form-name">
                        </div>
                        <div class="form-group">
                            <label for="">Ingredients</label>
                            <textarea class="form-control" name="ingredients" id="edit-form-ingredients"></textarea>
                        </div>
                        <div class="form-group">
                            <label for="">Directions</label>
                            <textarea class="form-control" name="directions" id="edit-form-directions"></textarea>
                        </div>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                        <input type="submit" class="btn btn-primary" value="Save" />
                    </div>
                </form>
            </div>
        </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

next we are going to modify our main.js.

$('.edit-recipe').on('click',()=>{
        $('#edit-form-name').val($(this).data('name'));
        $('#edit-form-ingredients').val($(this).data('ingredients'));
        $('#edit-form-directions').val($(this).data('directions'));
        $('#edit-form-id').val($(this).data('id'));
    })
Enter fullscreen mode Exit fullscreen mode

3 . But there is a problem with the code, it will not show the database data when we clicked the update button. The issue here is likely related to the use of the arrow function (=>). In arrow functions, this does not have its own context and instead inherits it from the enclosing lexical context. Therefore, $(this) does not refer to the element being clicked in this case. So we remove the arrow function and add function, here is the updated code,

$('.edit-recipe').on('click', function(){
        $('#edit-form-name').val($(this).data('name'));
        $('#edit-form-ingredients').val($(this).data('ingredients'));
        $('#edit-form-directions').val($(this).data('directions'));
        $('#edit-form-id').val($(this).data('id'));
    })
Enter fullscreen mode Exit fullscreen mode

add we add the post method to our app.js.

app.post('/edit', (req, res)=>{
    const pool = new Pool({
        connectionString: connect
    });

    pool.query('UPDATE recipes SET name=$1, ingredients=$2, directions=$3 WHERE id = $4' , [req.body.name, req.body.ingredients, req.body.directions, req.body.id], (err, result)=>{
        if(err){
            console.error("Error running query", err);
            res.status(500).send("error retrieving data");
        }
        else{
            res.redirect('/');
        }
        //release the client from the pool
        pool.end(); 
    })
})
Enter fullscreen mode Exit fullscreen mode

And this ends our project. But we are not able to solve the issue of not showing the name of the ingredients.


Folder format

Full code base github link:

GitHub Link

Top comments (1)

Collapse
 
rifat87 profile image
Tahzib Mahmud Rifat

feel free to give feedback, the loop holes are available in every projects.