Beginner Friendly Project Recipe APP ( NodeJs & PostGreSql)
Tags: webdev, javascript, beginners, tutorial
Published: 2024-03-23 17:38:54


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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vnku9463m05xdw76lbp9.png)


4. In **General** give a name like: projectRecipe
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6lslphlbr6ovai458tca.png)



5. Then go to **Definition** and give the password.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8c21ej3fkx4w7hx3cq6t.png)



6. Now go to **privileges** and give permission to 
    - Can login
    - Superuser
    - Create roles
    - Create databases
    - Inherit rights from the parent roles
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gegul7bhxbfucxdl9ce7.png)


7. And click save.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d2mk7rvgrw8zr96bo32u.png)


####Creating new Database

1. Go to Databases and right click on it. Select Create and then Select Database.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z4gjk5mkq3cxnk34z3hc.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d9svxblr9aye21tvuiu1.png)


####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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dz6tvawmkp5smm7jdkwd.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jdcajaquu3vjo0nif4gz.png)

3. Now we are going to create column by clicking on tables -> columns -> create -> column.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nwy4z43aod0y39mggwgo.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o5ripyvjcr0x7iyfcpim.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m6jv0gska7fmw610has0.png)

####Setting id as PRIMARY KEY
5. To set the **id** as primary key at first we go to constraints and then create and select Primary Key.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yzwej2q8rstmvocybvze.png)

Now we set the primary key name as **pri_key** from general.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lri2hoa1q68gzpcg6x53.png)

next we go to definition and select the id column.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wrylruon6fqrxwph0wn3.png)

In SQL we can see the query.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/74tk5s0flw48kn1gmpxp.png)


6. Now we are going to create another column called name.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tffu0gtgkzg6qn7fdx84.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v2xfcebvwssea1cns68x.png)

same way we create **ingredient**, **update: changed it to, ingredients ** which data type is text. and **directions** column also.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/38b8m2alcdklrvs8fz6k.png)

                 #Database is done now



---


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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qpl5o21arlaazvv4cggw.png)


3. 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b39zhtc7hwhvrg0du60i.png)

- Give _description_ name as **Recipe Manager** 
- _entry point_ : app.js
- _author_ : Tahzib Mahmud Rifat,
- type yes .

that's it.
 <hr>
 
4 . Adding dependencies on package.json file.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ed7bmkld4ly57jke1mun.png)

```"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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s3s2vrkq7wma5clyknnm.png)


```
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();
```
2.  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');
});
```

3. Now, if we run **node app** on terminal we will see and it means the server is started, 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2uvat7hxodxybdapzaxn.png)

####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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/72tbq1v7817ndw3qoanf.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n3f8a3y08v06dly4vo17.png)

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>
```

and in index.dust we are going to add
```
{>"layout" /}

{<body}
THis is our index dust file.
{/body}
```

Now, go to your browser and search `localhost:3000` and it will show the update.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e2k0z6e4juc2w7x7sqi1.png)

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/](link) 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8bjuoeqydxqm2wce8s0c.png)

2. Now we copy the bootstrap.css from the downloaded file and paste it to out project **public->css** folder.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/adc333y3u14q2iu6mwtz.png)

same way we will copy the bootstrap.js file to our **public->js** folder.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o1kzeqszpwrz15a65jmd.png)

3. Let's go to layout.dust now. And we will simply link the **bootstrap.css, style.css and bootstrap.js** files.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b8jnxnt1t57vkmv9yht5.png)   

4. 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ubiu99181wkar5apk77.png)

5. Now, if we search `localhost:3000`, it will show us the modified fonts and version which means bootstrap has been implemented.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5qk9hwbf2ss8e7rduxvj.png)

6. 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>
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cz4qztl894gq6dngrsfz.png)

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

8. Adding some data to our database using **pgadmin**. Go to **recipe** table and click on that **All Rows**.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/etk6ctt2zp1xofecrltv.png)

Next go to Data Output and add your data. Do not add **id** because it will increase automatically.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yhpqpk5wbhv7sfkajji0.png)



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pn5qsd6nawx77r6zklnq.png)



---
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();
        });
    });
});
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dxd15i3cwy2cwyqmnk8o.png)

**Error**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fg05gtpfglxatcw4vyuy.png)


---
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qitvgulfej1ghonsihja.png)

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');
});

```

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](https://getbootstrap.com/docs/4.0/components/collapse/)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7julbbw2tllusiqzqhp6.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/il4tmbt8x5vgswb20yu6.png)

2. Now add a new div for collapse data after h4,
```<div class = "collapse" id="recipe_{id}">
                TEST
            </div>```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ulx5irdaw9c547yow2n.png)

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}
```
and our website,
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/94fk1783rgxaxv3f44ix.png)



---
3/20/24

<br>
###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/>
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rzwqewpzz5k571uy1ww3.png)

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>
```
This will give us,

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cyf6x72fw3fg2wlzuqby.png)

And after clicking on save btn we will get **/add**.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8rfb9dugvh3tkmt2th5n.png)



---


3/21/24
2. 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(); 
    });
});
```



---
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nonhf6irtxfssyttz378.png)

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);
                }
            })
        }
    })
})
```

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(); 
    });
});
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oxq9egu9idd9k9qdvqnr.png)

**Delete Button**
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ii0pkaqvb1029ilvvup9.png)

**Update Button**
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ffyg4i11r9i075taujab.png)

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>
```![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yg5b49sox8yy3zounsyx.png)



 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>
```

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'));
    })
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9aimb76vkgpb2k4gfc68.png)

 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'));
    })
```

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(); 
    })
})
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h23j1b85rb5uyc0qeigu.png)


**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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nqjuiu2yvjo43wc0fqq6.png)


####Full code base github link: 

[GitHub Link](https://github.com/Rifat87/RecipeApp/tree/main)