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
    Image description

  4. In General give a name like: projectRecipe
    Image description

  5. Then go to Definition and give the password.
    Image description

  6. Now go to privileges and give permission to

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

Creating new Database

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

  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.
    Image description

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.
    Image description

  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.
    Image description

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

  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.
    Image description
    Image description

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. Image description

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

next we go to definition and select the id column.
Image description

In SQL we can see the query.
Image description

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

Image description

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

             #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
    Image description

  3. Image description

  • 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.
Image description

"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 Image description
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, Image description

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.
    Image description

  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
    Image description

  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.
Image description

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/

Image description

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

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

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

  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>

Image description

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

  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

Image description

  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.
    Image description

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

Image description


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

Image description

Error

Image description


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.
Image description

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

Image description

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

paste it to after class,
Image description

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

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

Image description

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,
Image description


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

Image description

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,

Image description

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


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.
    Image description

  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
Image description

Delete Button
Image description

Update Button
Image description

  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


Image description

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

Image description

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

Image description

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


Folder format

Image description

Full code base github link:

GitHub Link

Top comments (1)

Collapse
 
tmrifat87 profile image
Tahzib Mahmud Rifat

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