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
- Install postgresql. Open pgAdmin.
- login with password.
Go to Login/Group Roles, right click on that and select create
-
Now go to privileges and give permission to
- Can login
- Superuser
- Create roles
- Create databases
- Inherit rights from the parent roles
Creating new Database
Go to Databases and right click on it. Select Create and then Select Database.
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
In recipebookdb we can see schemas and there we will have public and then tables. We are going to create a new table now.
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.
Now we are going to create column by clicking on tables -> columns -> create -> column.
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
- 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.
- 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
3/15/24
Setting Up environment
- 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
- 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();
- 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');
});
- 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.
At first we are going to create some folders and files, Folders: public, css, js, views. Files: style.css , index.dust, layout.dust.
-
In index.dust type,
This is our index dust file.
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 searchlocalhost:3000
and it will show
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>
© 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.
Next well see some postgresql, bootsrap and all.
3/17/24
Installing Bootstrap and Fetching data from Postgres
- Download bootstrap zip file from the link or just search download bootstrap. https://getbootstrap.com/docs/4.0/getting-started/download/
- 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.
Let's go to layout.dust now. And we will simply link the bootstrap.css, style.css and bootstrap.js files.
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>
Now, if we search
localhost:3000
, it will show us the modified fonts and version which means bootstrap has been implemented.
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>
Now we are going to modify our style.css.
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
- 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();
});
});
});
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');
});
- 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}"
- 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}
3/20/24
Add recipes to the application
- 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/>
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">×</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,
And after clicking on save btn we will get /add.
3/21/24
- 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
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.
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,
- 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>
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">×</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'));
})
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();
})
})
And this ends our project. But we are not able to solve the issue of not showing the name of the ingredients.
Top comments (1)
feel free to give feedback, the loop holes are available in every projects.