I am trying to create a small recipe app to learn express and mongoDB.
I created a form and I am able to get the values back and sent to the DB , but what i would like would be to be able to add ingredients in an array in the form and then export the array alongside the other values.
Here is my server file
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/recipes', {useNewUrlParser: true, useUnifiedTopology: true})
.then(() => {
console.log('connected to DB')
})
.catch(e => {
console.log('Something went wrong');
console.log(e);
})
const ingredients = require('atittude');
const express = require('express');
const app = express();
const path = require ('path');
const methodOverride = require ('method-override');
const fs = require('fs');
const multer = require('multer');
const port = 3000;
app.use(express.static(path.join(__dirname, '/public')))
app.use(express.urlencoded({extended: true}))
app.use(methodOverride('_method'))
app.set('view engine', 'ejs')
app.set('views', path.join(__dirname, '/views'))
const recipeSchema = new mongoose.Schema({
name : {
type: String,
required: true
},
category : {
type : [String],
required : true,
enum : ['comfort', 'healthy', 'favorite', 'quick', 'cheap', 'fancy']
},
time : Number,
ingredient: [String],
recipeBody : String,
img: {
data: Buffer,
contentType: String
}
})
const Recipe = mongoose.model('Recipe', recipeSchema);
app.get('/', (req, res) => {
res.render('home')
})
app.post('/add', (req,res) => {
const {name, category, time, ingredient, recipeBody} = req.body
console.log(`${name},${category},${time},${ingredient},${recipeBody},`)
const img = req.file
const recipe = new Recipe({name, category, time, ingredient, recipeBody})
recipe.save()
.then(data => {
console.log("It worked.")
console.log(data);
})
.catch(err => {
console.log('error : ')
console.log(err)
})
res.render('home', {name, category, time, ingredient, recipeBody})
})
app.get('/add', (req, res) => {
res.render('new')
})
app.listen(3000, () => {
console.log(`Listening on port ${port}`);
})
Here is my ejs file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>New Recipe</title>
</head>
<body>
<h1>Add a new recipe</h1>
<form action="/add" method="POST" id="mainform" enctype="x-www-form-urlencoded">
<label for="name">Name : </label>
<input type="text" placeholder="Name of the recipe..." name="name">
<label for="category">Categories : </label>
<select id="category" name="category" size="6" multiple>
<option value="comfort">Comfort</option>
<option value="healthy">Healthy</option>
<option value="favorite">Kids' favorites</option>
<option value="quick">Quick</option>
<option value="cheap">Cheap</option>
<option value="fancy">Fancy</option>
</select>
<label for="time">Time : </label>
<input name="time" type="text">
<label for="ingredient">Add ingredient </label>
<input name="ingredient" id="ingredient" type="text">
<button id="addingredient">Add !</button>
<textarea id="recipe" name="recipeBody" rows="4" cols="50">
Recipe here...
</textarea>
<input type="file" name="img" />
<button>Submit</button>
</form>
<ul id="ingList">
</ul>
<script type="module" src="/js/atittude.js"></script>
</body>
</html>
and here is my js file to add ingredients to the page
const adding = document.querySelector('#addingredient');
let currentIngredient = document.querySelector('#ingredient');
let ingredients = [];
const list = document.querySelector('#ingList');
adding.addEventListener("click", (event) =>{
event.preventDefault()
ingredients.push(currentIngredient.value)
console.log(ingredients);
const newIngredient = document.createElement('li')
newIngredient.innerText = ingredients[ingredients.length-1];
list.append(newIngredient);
export {ingredients};
})
I get an error atittude.js:13 Uncaught SyntaxError: Unexpected token 'export
I would like to be able to export my ingredients array to app.js to then send it to mongo.
Or should i do code the option to add ingredients to the list server side on in my ejs file using <%= ?
Sorry if some of the code is bad practice i'm learning.
Thanks a lot.
that's good that you're learning. If I get your question correctly, you are trying to send the array of ingredients to your server in order to save it in your DB. There are many ways to solve your problem here and they all involve sending a request from your client (the browser with the rendered ejs file) to your server (your nodejs app).
A straightforward way you can do this is to use the fetch api (you can check out the Mozilla Docs, they have excellent tutorias) to POST the ingredients like so:
fetch('/add', {
method: 'post',
body: JSON.stringify(ingredientsObj)
})
.then(function(response) {
// Do stuff with the response
})
.catch(function(error) {
console.log('Looks like there was a problem: \n', error);
});
You might want to consider securing your request with access controls and other parameters, as well as sanitizing your input to make sure it arrives in the right format you expect.
Related
I'm trying to make a simple to do list and I can't figure out the "delete" button. I don't get any errors, the buttons just doesn't do anything. I tried a lot of things but just can't seem to get it working. I'm not sure if the problem is with form action in the ejs file or with the app.delete in my index.js or both. Thank you in advance!
ejs file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>To Do List</title>
</head>
<body>
<h1>To Do List</h1>
<form action="/todo" method="post">
<input type="text" name="task">
<button>Add</button>
</form>
<ul>
<% for(let c of todos) { %>
<li>
<%= c.task %>
<form action="/todo/<%=c.id%>?_method=DELETE" method="POST"><button>Delete</button></form>
<%= c.id%>
</li>
<% } %>
</ul>
</body>
</html>
index.js file:
const express = require('express');
const app = express();
const path = require('path');
const methodOverride = require('method-override');
const { v4: uuid } = require('uuid');
uuid();
app.use(methodOverride('_method'));
app.use(express.urlencoded({ extended: true }));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
let todos = [
{
id: uuid(),
task: '1st task, testing'
}
]
//TO DO INDEX
app.get('/todo', (req, res) => {
res.render('todo', { todos });
})
//CREATE NEW TASK
app.post('/todo', (req, res) => {
const { task, id } = req.body;
todos.push({ task, id: uuid() });
res.redirect('todo')
})
//DELETE TASK
app.delete('/todo/:id', (req, res) => {
const { id } = req.body;
todos = todos.filter(c => c.id !== id);
console.log(todos);
res.redirect('/todo')
})
app.listen(3000, () => {
console.log('LISTENING ON PORT 3000')
})
You have configured the delete route in your express app with method type DELETE that is app.delete(…), but in your html form
<form action="/todo/<%=c.id%>?_method=DELETE" method="POST">
The method is mentioned as POST. So express is not accepting it.
Form in html allows only GET and POST request.
You’ll need to trigger those delete api calls using fetch api or jquery or ajax.
Also i am not sure why that _method=DELETE is written in form action. According to me it not need there if your frontend is jinja/Django. If it’s something else then I’m not sure.
To get more insights about why it isn’t working, open the network tab in the browsers console and click your button. It should send a network call to you backend and you should be able to see there why backend isn’t doing anything for that call.
SOLVED IT
I just had to change const { id } = req.body; to const { id } = req.params;
I am trying to create a basic user registration system. All I just want right now is that once I register with the HTML form, I should be able to console.log it in the server. I tried doing this using fetch but I get this error:
Fetch API cannot load file:///C:/api/register. URL scheme "file" is not supported.
This is the code:
index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Register</title>
</head>
<body>
<h1>Registration</h1>
<form id="reg-form">
<input type="text" placeholder="Username" id="username" autocomplete="off"/>
<input type="password" placeholder="Password" id="password"/>
<input type="submit" value="Submit Form"/>
</form>
</body>
<script>
const form = document.getElementById('reg-form')
form.addEventListener('submit', registerUser)
async function registerUser(event) {
event.preventDefault()
const username = document.getElementById('username').value
const password = document.getElementById('password').value
const result = fetch('/api/register', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username, password})
}).then(res => res.json())
console.log(result)
}
</script>
</html>
Express Server:
const express = require('express')
const path = require('path')
const bodyParser = require('body-parser')
const app = express()
app.use(bodyParser.json())
app.post('/api/register', async (req, res) => {
console.log(req.body)
res.json({status: 'ok'})
})
app.listen(4000, () => console.log('Server up at 4000'))
I also tried doing the POST request directly from the HTML form element by setting the method attribute to POST and the action attribute pointing to the post route in my express server. When I try to submit the form, it redirects me to an empty page that says "Your file couldn't be accessed. It may have been moved, edited, or deleted."
What can I do to fix this?
You're opening the html file manually, not serving it from the server, so /api/register isn't going to your backend. Either serve the file from your server, or fetch localhost:4000/api/register
To serve your file from the server, you can do this:
const path = require('path');
...
app.get('/', (req, res) => {
res.sendFile(path.resolve(__dirname, '../relative/path/to/index.html'));
});
Instead of fetching /api/register try http://localhost:4000/api/register
also to print the result try this:
fetch('http://localhost:4000/api/register', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username, password})
})
.then(res => res.json())
.then(data => console.log(data))
Im working in an application with a front and a back end, my back end written in nodejs, using express, multer and mongoose, is working pretty fine when i send data through postman, but as soon as i try the same request in my client made with html,css and vanilla javascript, it doesnt work, it gives me the following error:
TypeError: Cannot read property 'path' of undefined (which is pointing towards my network file that manages connections for a specific entity).
Despite the error mentioned above, i've been trying to send a file and a string from the form in html to the network file in my node server, but i keep getting the same 2 or so errors in terminal.
Here is some code so you can understand better.
server.js (main entry point)
const express = require('express');
const app = express();
const server = require('http').Server(app);
const path = require('path');
const cors = require('cors');
const bodyParser = require('body-parser');
const db = require('./db');
const router = require('./network/routes');
const config = require('./config');
db(config.dbUrl);
app.use(cors());
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(`${config.publicRoute}`, express.static(path.join(__dirname, 'public')));
server.listen(config.port, function() {});
console.log(`Application listen on ${config.host}${config.port}`);
router(app);
network.js (file that manages connections for "banner" entity)
const express = require('express');
const multer = require('multer');
const router = express.Router();
const response = require('../../network/response');
const controller = require('./controller');
const path = require('path');
const storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, path.join(__dirname, '../../public/files'))
},
filename: function(req, file, cb) {
cb(null, path.extname(file.originalname))
}
})
const upload = multer({ storage: storage })
router.post('/', upload.single('file'), function(req, res) {
console.log(req.body);
controller.addBanner(req.body.name, req.file, req.file.path)
.then((fullMessage) => {
response.success(req, res, fullMessage, 201);
})
.catch(e => {
response.error(req, res, 'Unexpected error', "Simulated Error", 400, e);
});
});
router.get('/', function(req, res) {
controller.getBanners()
.then((banners) => {
response.success(req, res, banners, 200);
})
.catch(e => {
response.error(req, res, 'Internal Error', 500, e);
})
});
router.delete('/:id', function(req, res) {
controller.deleteBanner(req.params.id)
.then(() => {
response.success(req, res, `Imagen con id: ${req.params.id} ,eliminada`, 200);
})
.catch(e => {
response.error(req, res, 'Internal Error', 500, e);
})
});
module.exports = router;
panel.html (where the form that supposedly sends the post request lies)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- <meta http-equiv="Content-Security-Policy" content="default-src https://cdn.example.net; child-src 'none'; object-src 'none'"> -->
<link rel="stylesheet" type="text/css" href="css/panel-style.css">
<script defer src="panel-script.js"></script>
<title>Panel de Control</title>
</head>
<body>
<header>
<img src="assets/mrvapeslogo.png" alt="mrvapes logo">
</header>
<section>
<form accept-charset="UTF-8" enctype="multipart/form-data" action="../components/banner/network.js" autocomplete="off" method="GET" target="_blank">
<label for="user">Banners Activos</label><br/>
<ul class="files-container">
</ul>
</form>
<form accept-charset="UTF-8" enctype="multipart/form-data" action="http://localhost:3000/banner" autocomplete="off" method="POST" target="_blank">
<div class="container">
<div class="button-wrap">
<!-- <label class="button" for="upload">Subir</label> -->
<input type="text" id="name" placeholder="Nombre de Archivo" value="" name="name" required>
<input id="image" value="Subir" placeholder="Subir Archivo" type="file" required>
<button id="upload" name="send-post--request" value="post-request" type="submit">Enviar</button>
<!-- <input id="upload" type=" submit " value="Enviar " onclick="submit() "> -->
</div>
</div>
</form>
</section>
</body>
panel-script.js (client-side file that manages the logic for the http request)
const upload = document.getElementById("upload");
const image = document.getElementById("image");
const title = document.getElementById("name");
upload.addEventListener("click", (e) => {
console.log('im in bro');
e.preventDefault();
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
alert(xhr.response);
}
}
xhr.open("POST", 'http://localhost:3000/banner', true);
//xhr.setRequestHeader("Accept", "multipart/form-data");
//xhr.setRequestHeader('Content-Type', 'multipart/form-data');
var fileSent = {
"name": title,
"file": image
};
console.log(fileSent);
xhr.send(fileSent);
alert('Subida exitosa!');
})
function retrieve() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
alert(xhr.response);
}
}
xhr.open('get', 'http://localhost:3000/banner', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
xhr.send();
}
I'm all ears towards your suggestions, thanks in advance fellow developers.
You need to provide the name for the <input type="file">. It must match upload.single('file').
<input id="image" name="file" value="Subir" placeholder="Subir Archivo" type="file" required>
In Express 4 req.file is no longer available on the req object by default. I see you are using multer middleware to handle that. The error is likely coming from multer in network.js when you try to access req.file.path. The req.file is undefined because upload.single('file') is looking for form data named file, and you don't have name="file" in the form input.
-EDIT-
I see now you are not trying to send the file with the native HTML Form submit (which should work now with my answer), and you are listening to submit button clicks to send with XMLHttpRequest in panel-script.js. Sending a multipart/form-data with XMLHttpRequest is only supported with XHR FormData API (which is only available to new browsers). See the answer here if you wish to continue with using panel-script.js: Send a file as multipart through XMLHttpRequest
In your case you would have to use:
var formData = new FormData();
formData.append("file", document.getElementById("image").files[0]);
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:3000/banner");
xhr.send(formData);
Just solved the problem, adding to the response i got from fellow DSander, i actually needed to put the name to my html tag, and i dont know if its right but what solved the problem is deleting the script tag in my html so it doesnt use the send method i wrote.
Commented this
<script defer src="panel-script.js"></script>
and stopped using panel-script.js
i have an express server which has the code:
// Setup empty JS object to act as endpoint for all routes
projectData = {};
// Require Express to run server and routes
const express = require('express');
// Start up an instance of app
const app = express();
/* Dependencies */
const bodyParser = require('body-parser');
/* Middleware*/
//Here we are configuring express to use body-parser as middle-ware.
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Cors for cross origin allowance
const cors = require('cors');
app.use(cors());
// Initialize the main project folder
app.use(express.static('website'));
const port = 8000;
// Setup Server
const server = app.listen(port, listening);
function listening(){
// console.log(server);
console.log(`running on localhost: ${port}`);
};
app.get('/all', sendData);
function sendData (req, res) {
res.send(projectData);
};
app.post('/all', receiveData);
function receiveData (req, res) {
projectData.temp = req.body.temp;
projectData.date = req.body.date;
projectData.ures = req.body.ures;
res.send(projectData);
console.log(projectData);
}
so simply i am making a weather journal app which receives data from an API and shows it to the user and this is the index look like:
so basically the user is going to write the zip code and feelings and when clicking on the button generate the entry should contain the information like this
but when the user click the button generate this shows and the user have to click it again in order to show the information like the top picture:
and this is the JavaScript code for the client-side:
/* Global Variables */
let baseURLz = 'http://api.openweathermap.org/data/2.5/weather?zip=';
let baseURLa = '&appid=';
const apiKey = '8e017daef09d7f784c8595696bdcb587';
// Create a new date instance dynamically with JS
let d = new Date();
let newDate = d.getMonth()+'.'+ d.getDate()+'.'+ d.getFullYear();
document.getElementById('generate').addEventListener('click', performAction);
function performAction(e){
const zipcode = document.getElementById('zip').value;
const ures = document.getElementById('feelings').value;
getWeather(baseURLz, zipcode, baseURLa, apiKey)
.then(function(data){
console.log(data);
postData('/all', {temp: data.main.temp, date: newDate, ures: ures});
})
.then(
updateUI()
)
}
const getWeather = async (baseURLz, zipcode, baseURLa, apikey)=>{
const res = await fetch(baseURLz+zipcode+baseURLa+apikey+'&units = imperial')
try {
const data = await res.json();
console.log(data)
return data;
} catch(error) {
console.log("error", error);
// appropriately handle the error
}
}
const postData = async ( url = '', data = {})=>{
const res = await fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data), // body data type must match "Content-Type" header
});
try {
const newData = await res.json();
return newData;
}catch(error) {
console.log("error", error);
}
};
const updateUI = async () => {
const req = await fetch('/all');
try{
const allData = await req.json();
document.getElementById('date').innerHTML = 'Date: '+allData.date;
document.getElementById('temp').innerHTML = 'Temperature: '+allData.temp+'°F';
document.getElementById('content').innerHTML = 'Feelings: '+allData.ures;
}catch(error){
console.log("error", error);
}
}
and this is the html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Weather Journal</title>
<link href="https://fonts.googleapis.com/css?family=Oswald:400,600,700|Ranga:400,700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id = "app">
<div class ="holder headline">
Weather Journal App
</div>
<div class ="holder zip">
<label for="zip">Enter Zipcode here</label>
<input type="text" id="zip" placeholder="enter zip code here">
</div>
<div class ="holder feel">
<label for="feelings">How are you feeling today?</label>
<textarea class= "myInput" id="feelings" placeholder="Enter your feelings here" rows="9" cols="50"></textarea>
<button id="generate" type = "submit"> Generate </button>
</div>
<div class ="holder entry">
<div class = "title">Most Recent Entry</div>
<div id = "entryHolder">
<div id = "date"></div>
<div id = "temp"></div>
<div id = "content"></div>
</div>
</div>
</div>
<script src="app.js" type="text/javascript"></script>
</body>
</html>
please i want to figure out where is the problem causing this to happen.
I'm trying to do a notify function in my website. The button his on HTML (client side) and on press calls a node js function to execute a python script that sends an e-mail to myself (tested and working).
This is my code on the client side (index.html)
<body>
...
<div class="form-popup" id="myForm">
<form class="form-container" name="form-owner">
<h1>Notify Owner</h1>
<label><b>Name</b></label>
<input id="name" style="width:90%" type="text" placeholder="Enter your name" required>
<label><b>Message</b></label>
<input id="context" style="width:90%" type="text" placeholder="Enter Reason" required>
<button type="submit" class="btn" onclick="notifyOwner()">Submit</button>
<button type="button" class="btn cancel" onclick="closeForm()">Close</button>
</form>
</div>
</div>
...
The code on the server side (app.js)
const express = require('express');
const child_process = require('child_process')
const app = express()
const cheerio = require('cheerio');
const port = 80
'use strict';
var request = require('request');
...
app.post("/api/notify", async (req, res) => {
try{
const $ = cheerio.load('<input id="name" style="width:90%" type="text" placeholder="Enter your name" required>');
var subject = $('[id=name]').text();
var body = "ok";
child_process.execSync('python3 sendEmail.py ' + subject + " " + body);
}catch(error){
console.error(error);
}
});
The varialbe 'subject' turns out as null and the script is not runned because that argument is missing
I believe there's some confusion here. Cheerio is used to parse and manipulate HTML strings, but that's not what your front end code is sending. The string you're telling Cheerio to manipulate has no relationship to the request form payload in any way, nor is it a necessary tool for processing the POST request.
You appear to be using JS to submit JSON or form data to the server (as opposed to an HTML form action). req.body and req.query would contain this parsed payload respectively, depending on how your server is set up.
Here's an example of how you can set this up using JSON. Note that I've promisified the exec function to avoid blocking the event loop with a synchronous subprocess call.
Also, the form name and context don't seem to correspond well with subject and body--I assume you'll make this consistent.
You'll want to escape and properly quote your subprocess argument string as well.
public/index.html:
<!DOCTYPE html>
<html lang="en">
<head><title>Test</title></head>
<body>
<form>
<h1>Notify Owner</h1>
<label>
<b>Name</b>
<input id="name" placeholder="Enter your name" required>
</label>
<label>
<b>Message</b>
<input id="context" placeholder="Enter Reason" required>
</label>
<button type="submit">Submit</button>
</form>
<script>
document.querySelector("form").addEventListener("submit", event => {
event.preventDefault();
const name = event.target.querySelector("#name").value;
const message = event.target.querySelector("#context").value;
fetch("/api/notify", {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({name, message})
})
.then(res => {
if (!res.ok) {
throw Error(res.statusText);
}
return res.json();
})
.then(data => {
console.log(data);
event.target.reset();
})
.catch(err => console.error(err));
});
</script>
</body>
</html>
server.js:
const {promisify} = require("util");
const exec = promisify(require("child_process").exec);
const express = require("express");
const app = express();
app.use(express.json());
app.use(express.static("public"));
app.post("/api/notify", async (req, res) => {
const {name, message} = req.body; // TODO validate
// do stuff with name and message
console.log(name, message);
try {
//await exec(`python3 sendEmail.py ${subject} ${body}`);
res.json({message: "email sent"});
}
catch (err) {
res.json({error: "failed to send email"});
}
});
app.listen(8000, () => console.log("listening on port 8000"));