I am working on a blogging application (click the link to see the GitHub repo) with Express, EJS and MongoDB.
Before submitting a new post, of course, I have to validate the form entries. I use express-validator version 6.3.0.
My addPost controller:
exports.addPost = (req, res, next) => {
// Form validation rules
check('title', 'The title field id required')
.not()
.isEmpty();
check('excerpt', 'The excerpt field id required')
.not()
.isEmpty();
check('body', 'The full text field id required')
.not()
.isEmpty();
const errors = validationResult(req);
if (!errors.isEmpty()) {
console.log(errors.array());
}
if (!errors.isEmpty()) {
res.render('admin/addpost', {
layout: 'admin/layout',
website_name: 'MEAN Blog',
page_heading: 'Dashboard',
page_subheading: 'Add New Post',
errors: errors
});
req.flash('danger', errors);
req.session.save(() => res.redirect('/dashboard'));
} else {
const post = new Post();
post.title = req.body.title;
post.short_description = req.body.excerpt
post.full_text = req.body.body;
post.save(function(err) {
if (err) {
console.log(err);
return;
} else {
req.flash('success', "The post was successfully added");
req.session.save(() => res.redirect('/dashboard'));
}
});
}
}
The Post model:
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
short_description: {
type: String,
required: true
},
full_text: {
type: String,
required: true
},
post_image: {
type: String,
required: false
},
updated_at: {
type: Date,
default: Date.now()
},
created_at: {
type: Date,
default: Date.now()
}
});
The error messages are nor rendered in the view, which looks like this:
<div id="messages" class="text-center">
<% Object.keys(messages).forEach(function (type) { %>
<% messages[type].forEach(function (message) { %>
<div class="alert alert-<%= type %>"><%= message %></div>
<% }) %>
<% }) %>
</div>
UPDATE:
The index.js file in the root has this code:
const express = require("express");
const dotenv = require("dotenv");
const mongoose = require("mongoose");
const path = require("path");
const morgan = require("morgan");
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const expressLayouts = require("express-ejs-layouts");
const flash = require("express-flash");
const session = require("express-session");
const app = express();
dotenv.config();
//Conect to MONGODB
mongoose
.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => {
console.log("conected");
});
mongoose.connection.on("error", err => {
console.log(`DB connection error: ${err.message}`);
});
// Set static directory
app.use(express.static(path.join(__dirname, "public")));
// Set views directory
app.set("views", path.join(__dirname, "views"));
// Set view engine
app.set("view engine", "ejs");
// Use Express Layouts
app.use(expressLayouts);
// Morgan Middleware
app.use(morgan("dev"));
// support parsing of application/json type post data
app.use(bodyParser.json());
//support parsing of application/x-www-form-urlencoded post data
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
// Express Sessions Middleware
app.use(session({
secret: '123',
resave: true,
saveUninitialized: true
}));
// Express Messages Middleware
app.use(flash());
app.use(function (req, res, next) {
res.locals.messages = require('express-messages')(req, res);
next();
});
// Bring the Dashboard
const dashboardRoute = require("./routes/admin/dashboard");
// Get Dashboard Routes
app.use('/dashboard', dashboardRoute);
What am I doing wrong?
exports.addPost = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
req.flash('errors', errors.array())
req.session.save(() => res.redirect('../addpost'));
//return res.status(400).send(errors.array());
} else {
const post = new Post();
post.title = req.body.title;
post.short_description = req.body.excerpt
post.full_text = req.body.body;
post.save(function(err){
if(err){
console.log(err);
return;
} else {
req.flash('success', "The post was successfully added");
req.session.save(() => res.redirect('/dashboard'));
}
});
}
}
messages.ejs
<div id="messages" class="text-center">
<% Object.keys(messages).forEach(function (type) { %>
<% messages[type].forEach(function (message) { %>
<% if (type === 'errors') {%>
<div class="alert alert-<%= type %>"><%= message.msg %></div>
<%} else { %>
<div class="alert alert-<%= type %>"><%= message %></div>
<% } %>
<% }) %>
<% }) %>
I guess this is what you intended to do
You are rendering a template then trying to show flash and then redirect again. Change it to this
req.flash('danger', errors);
req.session.save(() => res.redirect('/dashboard'));
Forget the render... It makes no sense for you to have it there. What render does, it renders and returns a template. Therefore your req.flash and redirect never happens or it happens after the header have already been sent.
res.render() definition:
Renders a view and sends the rendered HTML string to the client.
Optional parameters:
locals, an object whose properties define local variables for the
view. callback, a callback function. If provided, the method returns
both the possible error and rendered string, but does not perform an
automated response. When an error occurs, the method invokes next(err)
internally.
AND
exports.addPost = (req, res, next) => {
// Form validation rules
req.check('title').not().isEmpty().withMessage("The title field is mandatory");
req.check('body').not().isEmpty().withMessage("The full text field is mandatory");
const errors = req.validationErrors();
try change your if statement from this:
if (!errors.isEmpty()) {
console.log('there are no validation errors');
} else {
console.log(errors);
}
}
to this:
exports.addPost = (req, res, next) => {
// Form validation rules
check('title', '<your error message>')
.not()
.isEmpty();
check('excerpt', '<your error message>')
.not()
.isEmpty();
check('body', '<your error message>')
.not()
.isEmpty();
const errors = validationResult(req);
const errors = validationResult(req);
if (!errors.isEmpty()) {
console.log(errors.array());
}
}
Edit
If you would like to send an response to your front-end replace the console.log() command into res.send() then parse the answer in your front-end
like so:
if (!errors.isEmpty()) {
return res.send(errors.array());
// can also send an status and catch it
// by sending res.status(400).send(errors.array());
}
Hopefully this makes sense
from what i see in the documentation of express-validator you need to provide an array of validation rules(those checks at the top of your controller) when you define the route.
It doesn't make much sense for them to be at the top of the request handler since the express-validator won't be able to access the context that provides the request to be validated.
So in the router you need something like this:
router/front-end/posts.js
const validationRules = [// Form validation rules
check('title', 'The title field id required')
.not()
.isEmpty(),
check('excerpt', 'The excerpt field id required')
.not()
.isEmpty(),
check('body', 'The full text field id required')
.not()
.isEmpty()];
// create new post
router.post('/', validationRules, postsController.addPost);
controllers/front-end/posts.js
exports.addPost = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
console.log(errors.array());
}
if (!errors.isEmpty()) {
res.render('admin/addpost', {
layout: 'admin/layout',
website_name: 'MEAN Blog',
page_heading: 'Dashboard',
page_subheading: 'Add New Post',
errors: errors
});
req.flash('danger', errors);
req.session.save(() => res.redirect('/dashboard'));
} else {
const post = new Post();
post.title = req.body.title;
post.short_description = req.body.excerpt
post.full_text = req.body.body;
post.save(function(err){
if(err){
console.log(err);
return;
} else {
req.flash('success', "The post was successfully added");
req.session.save(() => res.redirect('/dashboard'));
}
});
}
}
Everything else seem ok, at least from the code you posted.
it's not a bug with express-validators, it is the way how validators work in case of middlewares.
At the root level create a directory called utils and inside the directory a validation.js file and add your validation code in it:
utils/validation.js
const { check } = require('express-validator');
exports.addPostCheck = [
check('title', 'The title field id required')
.not()
.isEmpty(),
check('excerpt', 'The excerpt field id required')
.not()
.isEmpty(),
check('body', 'The full text field id required')
.not()
.isEmpty()
];
In the routes/dashboard.js include validation.js
const validator = require('../../utils/validation.js');
Change Line No: 16
From:
router.post('/post/add', dashboardController.addPost);
To:
router.post('/post/add', validator.addPostCheck, dashboardController.addPost);
In the controllers/admin/dashboard.js
Change Line No: 2
From:
const { check, validationResult } = require('express-validator');
To:
const { validationResult } = require('express-validator');
Remove Line Nos 29 to 39.
Reference
Maybe you should start with this https://express-validator.github.io/docs/ then gradually customise it to fit your need so that you can catch any error along the way.
use this code in index.js to get flash error messages locally in EJS ,
app.use(function (req, res, next) {
res.locals.messages = req.flash();
next();
});
I have applied the solution provided by Saravanakumar T N with a small modification in messages.ejs.
I have: this in the controller:
exports.addPost = (req, res, next) => {
const errors = validationResult(req);
const post = new Post();
if (!errors.isEmpty()) {
req.flash('danger', errors.array());
req.session.save(() => res.redirect('../addpost'));
} else {
post.title = req.body.title;
post.short_description = req.body.excerpt
post.full_text = req.body.body;
post.save(function(err) {
if (err) {
console.log(err);
return;
} else {
req.flash('success', "The post was successfully added");
req.session.save(() => res.redirect('/dashboard'));
}
});
}
}
In the view:
<div id="messages" class="text-center">
<% Object.keys(messages).forEach(function (type) { %>
<% messages[type].forEach(function (message) { %>
<div class="alert alert-success <% if (type === 'danger') { %> alert-dismissible <% } %> alert-<%= type %>">
<% if (type === 'danger') { %>
<button type="button" class="close" data-dismiss="alert">×</button>
<%= message.msg %>
<%} else { %>
<%= message %>
<% } %>
</div>
<% }) %>
<% }) %>
</div>
Related
I tried very much , it's been more than a month that passport js local authentication is not authenticating or not working. I sent login data from React through axios. How to do login properly and print "logged in" in console.
Sigin.jsx
React page from where i sent login info
import React, {useState, useEffect} from "react";
import GoogleIcon from '#mui/icons-material/Google';
import FacebookIcon from '#mui/icons-material/Facebook';
import LinkedInIcon from '#mui/icons-material/LinkedIn';
import axios from "axios";
function Signin(props) {
// useEffect(() => {
// axios.post("http://localhost:8080/login",{
// body: userInData
// })
// },[])
const [userInData, setUserInData] = useState({
username: "",
password: ""
});
function LogIn(event){
axios.post("http://localhost:8080/login",{
data: userInData
}).then((data) => {
console.log("login done");
console.log(data);
}).catch(err => {
console.log("login error");
console.log(err);
})
props.userLogin(userInData);
setUserInData({
username: "",
password: ""
});
event.preventDefault();
}
function userIn(event) {
const {name, value} = event.target;
setUserInData((prevData) => {
return {
...prevData,
[name]: value
}
})
}
return(
<div className="form-container sign-in-container">
<form action="#">
<h1>Log in</h1>
<div className="social-container">
<i className="fab fa-facebook-f"><GoogleIcon/></i>
<i className="fab fa-google-plus-g"><FacebookIcon/></i>
<i className="fab fa-linkedin-in"><LinkedInIcon/></i>
</div>
<span>or create your own account</span>
<input onChange={userIn} name="username" value={userInData.email} type="email" placeholder="Email" />
<input onChange={userIn} name="password" value={userInData.password} type="password" placeholder="Password" />
Forgot your password?
<button onClick={LogIn}>Log In</button>
</form>
</div>
);
}
export default Signin;
here ignore all the others and you can see the axios.post to /login route to the backend.
Node.js
here is node js backend with mongodb
const mongoose = require("mongoose");
const bodyParser = require("body-parser");
const express = require("express");
const app = express();
const cors = require("cors");
const { Password } = require("#mui/icons-material");
const bcrypt = require("bcrypt");
const saltRounds = 10;
const passportInit = require("./LocalAuth.js");
//requiring for authentication, cookies & Sessions
const session = require("express-session");
const passport = require("passport");
const passportLocalMongoose = require("passport-local-mongoose");
app.use(cors({origin: true, credentials: true}));
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: false,
cookie: { secure: true }
}));
app.use(passport.initialize());
app.use(passport.session());
mongoose.set('strictQuery', true);
mongoose.connect("mongodb://localhost:27017/keeperDB",{useNewUrlParser: true});
//Schema and Model for SignUp/Login Id Psw
const SignupSchema = new mongoose.Schema({
name: { type: String, required: true},
username: { type: String, required: true },
password: { type: String, required: true }
})
SignupSchema.plugin(passportLocalMongoose);
const Signup = mongoose.model("Signup",SignupSchema);
passportInit(passport);
//for login data
//Idea 1
// app.post("/login",(req,res) => {
// const user = new Signup({
// username: req.body.data.username,
// password: req.body.data.password
// })
// console.log(user);
// try{
// req.login(user, function(err){
// if (!err) {
// passport.authenticate("local",(err,users,info) => {
// if(err) throw err;
// if(!users) res.send({message: "user doesnot exist."});
// else{
// res.send({message: "logged in."});
// }
// })
// } else {
// res.send({message: "bhayena"});
// // window.alert("Something went wrong! Please try again");
// }
// })
// }
// catch(err){
// res.send({message: err + "login error"})
// // console.log(err + "login error");
// }
// })
//Idea 2
app.post('/login', passport.authenticate('local'), function(req, res) {
//res.send("logged in from first file.");
console.log(res.user);
});
//Idea 3
// app.post("/login",(req, res, next) => {
// const user = new Signup({
// username: req.body.body.username,
// password: req.body.body.password
// })
// console.log(user);
// // const username = req.body.body.email;
// try {
// passport.authenticate("local",
// (err,users,info) => {
// if(err) throw err;
// if(!user) console.log("user doesnot exist.");
// else{
// req.login(users, err => {
// if(err) {
// // throw err;
// console.log("login failed");
// }
// console.log(req.user);
// console.log("succefully authenticated and logged in too");
// })
// }
// }
// // {successMessage: "login data sent.", failureMesssage: "login failed."}
// )
// // ,{successRedirect:"/login", failureRedirect:"/"}
// } catch (error) {
// console.log(err + "authentication not sent");
// }
// }
// )
//for new user registration
app.post("/register",(req,res) => {
Signup.findOne({username: req.body.body.username}, (err,found) => {
if(err){
window.alert("Something went wrong! Please try again");
}else{
if(!found){
// console.log(req.body.body.name);
// console.log(req.body.body.username);
// console.log(req.body.body.password);
bcrypt.hash(req.body.body.password,saltRounds,(err,hash) => {
const newId = new Signup({
name: req.body.body.name,
username: req.body.body.username,
password: hash
})
newId.save().then(() => {
console.log("saved");
res.send({message: "signup done be"});
})
.catch((err) => {
console.log("saving failed");
console.log(err);
res.send({message: "signup error"});
});
})
}else{
// window.alert("This email is already used!");
}
}
})
})
Here i tried all three login ideas. None of them worked.
I have mentioned /signup too but i works fine. Main problem is logging in.
LocalAuth.js
Here is the code for authentication that i have imported in Node.js file too
const LocalStrategy = require("passport-local").Strategy;
// const {Signup} = require("./Node");
const bcrypt = require("bcrypt");
// const passport = require("passport");
//this may be for just logging in but not for registration
function passportInit(passport){
console.log("reached to local strategy page");
passport.use(new LocalStrategy(
function(username, password, done) {
console.log("reached to local authentication phase");
Signup.findOne({ username: username }, function (err, user) {
if (err) { console.log("signup data error"); return done(err); }
if (!user) { console.log("user doesnot exist"); return done(null, false); }
bcrypt.compare(password,user.password,(err,result) => {
if(err) throw err;
if(result = true) {
console.log("verified");
return done(null, user);
}else{
console.log("not verified");
return done(null, false);
}
})
});
}
));
// used to serialize the user for the session
passport.serializeUser(function(user, done) {
done(null, user.id);
// where is this user.id going? Are we supposed to access this anywhere?
});
// used to deserialize the user
passport.deserializeUser(function(id, done) {
Signup.findById(id, function(err, user) {
done(err, user);
});
});
}
module.exports = passportInit;
SignUp.jsx
here is signup react file too if needed
import React, {useState, useEffect} from "react";
import GoogleIcon from '#mui/icons-material/Google';
import FacebookIcon from '#mui/icons-material/Facebook';
import LinkedInIcon from '#mui/icons-material/LinkedIn';
import axios from "axios";
import { Link } from "react-router-dom";
function Signup(props) {
const [user, setUser] = useState({
name:"",
username:"",
password:""
})
function userUp(event){
const {name, value} = event.target;
setUser((prevData) => {
return {
...prevData,
[name]: value
}
})
}
function signUp(event){
axios.post("http://localhost:8080/register",{
body: user
}).then((res) => {
// console.log({message: "signup done fe"});
console.log(res);
}).catch(err => {
console.log("signup error");
console.log(err);
})
props.userSignUp(user);
setUser({
name:"",
username:"",
password:""
})
event.preventDefault();
}
return(
<div className="form-container sign-up-container">
<form action="#">
<h1>Create Account</h1>
<div className="social-container">
<i className="fab fa-facebook-f"><GoogleIcon/></i>
<i className="fab fa-google-plus-g"><FacebookIcon/></i>
<i className="fab fa-linkedin-in"><LinkedInIcon/></i>
</div>
<span>or use your email for registration</span>
<input onChange={userUp} name="name" type="text" placeholder="Name" />
<input onChange={userUp} name="username" type="email" placeholder="Email" />
<input onChange={userUp} name="password" type="password" placeholder="Password" />
<button onClick={signUp} > Sign Up </button>
</form>
</div>
);
}
export default Signup;
I tried to log in but it didn't work. I know it may problem in login API or in local authentication code. I want to login properly.
Error: throw er; // Unhandled 'error' event
TypeError: Cannot read properties of null (reading 'items')
at C:\Users\shiva\Desktop\Web Development\todolist\app.js:105:17
getting the error in the 1st app.post method -> else statement. stuck at it for almost a day.
ps: code might not run as db link is changed by me
const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const _ = require("lodash");
const app = express();
app.set('view engine', 'ejs');
app.use(bodyParser.urlencoded({extended: true}));
app.use(express.static("public"));
mongoose.connect("mongodb+srv://adminshivam:refd3sfde#cluster0.3igye.mongodb.net/todolistDB");
const itemsSchema = {
name: String
};
const Item = mongoose.model("Item", itemsSchema);
const item1 = new Item({
name: "Welcome to your todolist!"
});
const item2 = new Item({
name: "Hit the + button to add a new item."
});
const item3 = new Item({
name: "<-- Hit this to delete an item."
});
const defaultItems = [item1, item2, item3];
const listSchema = {
name: String,
items: [itemsSchema]
};
const List = mongoose.model("List", listSchema);
app.get("/", function(req, res) {
Item.find({}, function(err, foundItems){
if (foundItems.length === 0) {
Item.insertMany(defaultItems, function(err){
if (err) {
console.log(err);
} else {
console.log("Successfully savevd default items to DB.");
}
});
res.redirect("/");
} else {
res.render("list", {listTitle: "Today", newListItems: foundItems});
}
});
});
app.get("/:customListName", function(req, res){
const customListName = _.capitalize(req.params.customListName);
List.findOne({name: customListName}, function(err, foundList){
if (!err){
if (!foundList){
//Create a new list
const list = new List({
name: customListName,
items: defaultItems
});
list.save();
res.redirect("/" + customListName);
} else {
//Show an existing list
res.render("list", {listTitle: foundList.name, newListItems: foundList.items});
}
}
});
});
app.post("/", function(req, res){
const itemName = req.body.newItem;
const listName = req.body.list;
const item = new Item({
name: itemName
});
if (listName === "Today"){
item.save();
res.redirect("/");
} else {
List.findOne({name: listName}, function(err, foundList){
foundList.items.push(item);
foundList.save();
res.redirect("/" + listName);
});
}
});
app.post("/delete", function(req, res){
const checkedItemId = req.body.checkbox;
const listName = req.body.listName;
if (listName === "Today") {
Item.findByIdAndRemove(checkedItemId, function(err){
if (!err) {
console.log("Successfully deleted checked item.");
res.redirect("/");
}
});
} else {
List.findOneAndUpdate({name: listName}, {$pull: {items: {_id: checkedItemId}}}, function(err, foundList){
if (!err){
res.redirect("/" + listName);
}
});
}
});
app.get("/about", function(req, res){
res.render("about");
});
app.listen(3000, function() {
console.log("Server started on port 3000");
});
error link from above code
foundList.items.push(item);
list.ejs
<%- include("header") -%>
<div class="box" id="heading">
<h1> <%= listTitle %> </h1>
</div>
<div class="box">
<% newListItems.forEach((listItem) => { %>
<form action="/delete" method="post">
<div class="item">
<input type="checkbox" name="checkbox" value="<%= listItem._id %>" onChange="this.form.submit()">
<p><%= listItem.name %></p>
</div>
<input type="hidden" name="listName" value="<%= listTitle %>"></input>
</form>
<% });%>
<form class="item" action="/" method="post">
<input type="text" name="newItem" placeholder="New Item" autocomplete="off">
<button type="submit" name="list" value="<%= listTitle %> ">+</button>
</form>
</div>
<%- include("footer") -%>
The problem seems to be at your schema, you are not making it the right way, instead of passing an object to moongose.model like this:
const itemsSchema = {
name: String
};
const Item = mongoose.model("Item", itemsSchema);
You should create a schema with the class Schema from mongoose, so try the following:
const mongoose = require("mongoose");
const { Schema } = mongoose
const itemsSchema = new Schema({
name: {type: String, required: true}
})
const Item = mongoose.model("Item", itemsSchema)
And in your listSchema do the following:
// removed const defaultItems = [item1, item2, item3];
const listSchema = new Schema({
name: {type: String, required: true},
// define an array of id that references the Item schema
items: [{type: ObjectId, ref: "Item", required: true, default: [] }]
});
const List = mongoose.model("List", listSchema);
// the object named as list1 will contain a reference to id of item1, item2 and item3.
const list1 = new List({name: "list1", items: [item1._id, item2._id, item3._id]}).save()
Further up, in your post api :
app.post("/", async function(req, res){
const itemName = req.body.newItem;
const listName = req.body.list;
try {
const item = new Item({ name: itemName });
if (listName === "Today") {
await item.save();
res.redirect("/");
} else {
// save the item id to items field from list schema
const list = await List.findOne({ name: listName })
list.items.push(item._id)
await list.save()
// don't make a redirect within the database query
res.redirect("/" + listName);
}
} catch(err) {
// If some error was threw handle it here
// you can handle mongoose errors as follow:
if(err instanceof mongoose.Error) {
// use a custom message or err.message
res.status(500).json({message: "error with Db"})
}
res.status(500).json({message: "something went wrong"})
}
});
See the docs to know more about it, as you are using references between your schemas I suggest you to take a look at the populate method because probably you will need it in the future. See the available queries from mongoose as well, almost everything you need is at the docs. Hope I helped you!
just remove the space in ejs file
<button type="submit" name="list" value="<%= listTitle %>😒">+</button>
So I converted the .hbs template to .ejs template and try to see the difference. I thought converting would be same, but it turns out it doesn't.
My code is working. I can register a user, but no error shows up. The error display is the main problem in my code.
Error-looping of .ejs
This is the .HBS version of Error
{{#if message}}
<h4 class="alert alert-danger mt-4">{{message}}</h4>
{{/if}}
This is the .EJS version of Error
//SignUp.js / SignIn.js
<% if (error) { %>
<h4 class="alert alert-danger mt-4">{{message}}</h4>
<% } %>
Another version of Error in .EJS
<% if (hasErrors) {%>
<div class="alert alert-danger">
<% messages.forEach(function(message){ %>
<p><%= message %></p>
<% });%>
</div>
<% }%>
This is the Controllers folder - auth.js
exports.signin = async (req, res) => {
try {
const {email, password} = req.body;
if(!email || !password) {
return res.status(400).render('shop/signin', {
message: 'Please provide an email and/or password'
});
}
con.query('SELECT * FROM users WHERE email = ?', [email], async (error, results) => {
console.log(results);
if(!results || !(await bcrypt.compare(password, results[0].password))) {
res.status(401).render('shop/signin', {
message: 'Email or Password is incorrect'
});
}
else {
const id = results[0].id;
const token = jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN
});
console.log("The token is: " + token);
const cookieOptions = {
expires: new Date(
Date.now() = process.env.JWT_COOKIE_EXPIRES * 24 * 60 * 60 * 1000
),
httpOnly: true
}
res.cookie('jwt', token, cookieOptions);
res.status(200).redirect("shop/profile");
}
});
}
catch(error) {
console.log(error);
}
}
exports.signup = (req, res) => {
console.log(req.body);
const {name, email, password, passwordConfirm} = req.body;
con.query('SELECT email FROM users WHERE email = ?', [email], async (error, results) => {
if(error) {
console.log(error);
}
if(results.length > 0) {
return res.render('shop/signup', {
message: 'That email is already in use!'
});
}
else if(password !== passwordConfirm) {
return res.render('shop/signup', {
message: 'Passwords do not match!'
});
}
let hashedPassword = await bcrypt.hash(password, 8);
console.log(hashedPassword);
con.query('INSERT INTO users SET ?', {name: name, email: email, password: hashedPassword}, (error, results) => {
if(error) {
console.log(error);
}
else {
console.log(results);
return res.render('shop/signup', {
message: 'User Registered!'
});
}
});
});
}
This is the Routes folder - user.js
router.post('/signup', authController.signup);
router.post('/signin', authController.signin);
module.exports = router;
Lord
You can solve this job in two ways.
1-Local Message
return res.send("<script> alert('Error Message'); window.location = 'shop/signin'; </script>")
2- If you don't want to use local messages Use 'flash' and 'session' packages
So, I created the Model, Routes, Controllers, everything seems alright looking at the code. I also did create my reducer, action, and component for the front end. As I am trying to create a product, it fires off the action and seems to be dispatching successfully, but in the redux devtools, I can see that the item I am getting in the products array is NULL. Also when I check my database, the product does not get saved. What is weird, is that I can see my image in my images folder, which is located at the backend. I do not know if I set up the action correctly, the component or the controller even, perhaps the routes. I would love if someone could look into this, perhaps my implementation is wrong. I am gonna link my Github and paste some codes down below.
Btw I am able to sign up, login and get users, so it is something specific to do with this feature.
Please also I am glad to change things in my app if you think some of my approaches are wrong, I am fairly new into this Stack <3
Link to Github: https://github.com/tigerabrodi/eBuy
server.js (Back End)
const express = require('express');
const connectDB = require('./config/db');
const uuidv4 = require("uuid/v4")
const colors = require("colors");
const morgan = require("morgan");
const multer = require("multer");
const path = require("path");
const app = express();
const PORT = process.env.PORT || 9045;
// Importing Routes
const authRoutes = require("./routes/auth");
const productRoutes = require("./routes/products");
// Connect Database
connectDB();
// Configuring Multer storage
const fileStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'images');
},
filename: (req, file, cb) => {
// I used a regex to trim the name from spaces
cb(null, uuidv4() + "_" + file.originalname.replace(/\s/g, ''));
}
});
const fileFilter = (req, file, cb) => {
if (
file.mimetype === 'image/png' ||
file.mimetype === 'image/jpg' ||
file.mimetype === 'image/jpeg'
) {
cb(null, true);
} else {
cb(null, false);
}
};
// Init Middleware
app.use(express.json({ extended: false }));
// Use Multer
app.use(
multer({ storage: fileStorage, fileFilter: fileFilter }).single('image')
);
// Serving static for images folder
app.use('/images', express.static(path.join(__dirname, 'images')));
// Dev logging middleware
if (process.env.NODE_ENV === "development") {
app.use(morgan("dev"));
}
// Define Routes
app.use('/auth', authRoutes);
app.use('/products', productRoutes);
// Serve static assets in production
if (process.env.NODE_ENV === 'production') {
// Set static folder
app.use(express.static('client/build'));
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
});
}
app.listen(PORT, () => console.log(`Server started on port ${PORT}`.cyan.underline.bold));
product routes (Back End)
const express = require("express");
const auth = require("../middleware/auth");
const {check} = require("express-validator");
const productController = require("../controllers/products");
const router = express.Router();
// Create Product
router.post("/", [
auth,
[
check("title", "Title is required").notEmpty(),
check("description", "Description is required").notEmpty(),
check("image", "Image is required").notEmpty(),
check("price", "Price is required").isNumeric()
]
], productController.createProduct);
module.exports = router;
Product Model (Back End)
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const ProductSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "User"
},
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
image: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
},
{
toJSON: {virtuals: true}
})
ProductSchema.virtual("question", {
ref: "Question",
localField: "_id",
foreignField: "product"
});
module.exports = mongoose.model("Product", ProductSchema);
product controller (Back end)
const {validationResult} = require("express-validator");
const User = require("../models/User");
const Product = require("../models/Product");
// #route POST /products
// #desc Add Product
// #access Private
exports.createProduct = async (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const {title, description, price} = req.body;
if (!req.file) {
return res.status(400).json({msg: "Please upload an Image!"})
}
const image = "/" + req.file.path.replace("\\" ,"/");
const newProduct = new Product({
user: req.user.id,
title,
description,
price,
image
});
const product = await newProduct.save();
res.status(201).json(product);
console.log(product);
next();
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
next(err);
}
}
product reducer (Front end)
import {ProductActionTypes} from "./product.types";
const initialState = {
products: [],
totalProducts: null,
product: null,
loading: true,
error: {}
}
const productReducer = (state = initialState, action) => {
const {payload, type} = action;
switch(type) {
case ProductActionTypes.ADD_PRODUCT:
return {
...state,
products: [payload, ...state.products],
loading: false
}
case ProductActionTypes.PRODUCT_ERROR:
return {
...state,
error: payload,
loading: false
}
default:
return state;
}
}
export default productReducer
product actions (Front End)
import {ProductActionTypes} from "./product.types"
import {setAlert} from "../alert/alert.actions"
import axios from "axios"
export const addProduct = (productData, history) => async dispatch => {
const config = {
headers: {
"Content-Type": "multipart/form-data"
}
};
const formData = new FormData();
formData.append("title", productData.title)
formData.append("description", productData.description)
formData.append("price", productData.price)
formData.append("image", productData.image)
try {
const res = axios.post("/products/", formData, config);
dispatch({
type: ProductActionTypes.ADD_PRODUCT,
payload: res.data
})
history.push("/dashboard");
dispatch(setAlert("Product created successfully", "success"))
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: {msg: err.response.statusText, status: err.response.status}
})
}
}
product types (front end)
export const ProductActionTypes = {
ADD_PRODUCT: "ADD_PRODUCT",
PRODUCT_ERROR: "PRODUCT_ERROR"
}
Product Component (Front End)
import React, {Fragment, useState} from 'react';
import {withRouter} from "react-router-dom";
import {connect} from "react-redux";
import {addProduct} from '../../redux/product/product.actions';
const CreateProduct = ({history, addProduct}) => {
const [formData,
setFormData] = useState({name: "", description: "", price: 10, image: ""});
const [showImage, setShowImage] = useState(false);
const [imageName, setImageName] = useState("")
const onSubmit = e => {
e.preventDefault();
addProduct(formData, history);
}
const onChangeImage = e => {
setFormData({...formData, image: e.target.files[0]});
setShowImage(true);
setImageName(e.target.files[0].name)
}
const onChange = e => setFormData({
...formData,
[e.target.name]: e.target.value
});
const {name, description, price} = formData;
return (
<Fragment>
<div className="container">
<div className="row">
<div className="col text-info font-weight-bold m-2">
*- All Fields Requried
<form onSubmit={e => onSubmit(e)}>
<div className="form-group m-2">
<label htmlFor="name">Name</label>
<input type="text" placeholder="Enter Products Name" name="name" value={name} onChange={e => onChange(e)} className="form-control" required/>
</div>
<div className="form-group m-2">
<label htmlFor="price">Price</label>
<input type="number" name="price" placeholder="Enter Products Price" value={price} onChange={e => onChange(e)} className="form-control" required/>
</div>
<div class="custom-file m-2">
<input type="file" onChange={e => onChangeImage(e)} class="custom-file-input bg-info" required/>
<label class="custom-file-label">{showImage ? imageName : "Upload Image"}</label>
</div>
<div className="form-group m-2">
<label htmlFor="title">Description</label>
<textarea name="description" onChange={e => onChange(e)} placeholder="Enter Products description" value={description} className="form-control" required/>
</div>
<input type="submit" value="Add Product" className="btn btn-block btn-info"/>
</form>
</div>
</div>
</div>
</Fragment>
);
}
export default connect(null, {addProduct})(withRouter(CreateProduct));
There is not much to say, as sulayman stated I forgot await. The other things were removing the headers since formData does that itself, and the image is accessible through req.file.path. I will post my controller and action down below for people to refer to it later.
product controller
// #route POST /products
// #desc Add Product
// #access Private
exports.createProduct = async (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
console.log(req.body);
const {name, description, price} = req.body;
if (!req.file) {
return res.status(400).json({msg: "Please upload an Image!"})
}
const image = "/" + req.file.path.replace("\\" ,"/");
const newProduct = new Product({
user: req.user.id,
name,
description,
price,
image
});
const product = await newProduct.save();
res.status(201).json(product);
console.log(product);
next();
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
next(err);
}
}
product action
export const addProduct = (productData, history) => async dispatch => {
const formData = new FormData();
formData.append("name", productData.name);
formData.append("description", productData.description);
formData.append("price", productData.price);
formData.append("image", productData.image);
try {
const res = await axios.post("/products", formData);
dispatch({
type: ProductActionTypes.ADD_PRODUCT,
payload: res.data
});
history.push("/dashboard");
dispatch(setAlert("Product created successfully", "success"))
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: {msg: err.response.statusText, status: err.response.status}
})
}
}
I am using the express-paginate module and I am running into some issues around the res.format section in my routes file. I am wondering why this portion is causing the paginate to not be found when I render my view file.
error message:
ReferenceError: /Users/user/Desktop/Projects/node/blog/views/pages/blog.ejs:7
5|
6| <body>
>> 7|
8| <header>
9| <% include ../partials/header %>
10| </header>
paginate is not defined
at buf.push.
</div>
</div>
</div>
<div class="paginate">
.
</div>
<footer>
.buf (eval at <anonymous> (/Users/user/Desktop/Projects/node/blog/node_modules/ejs/lib/ejs.js:242:14), <anonymous>:34:125)
at eval (eval at <anonymous> (/Users/user/Desktop/Projects/node/blog/node_modules/ejs/lib/ejs.js:242:14), <anonymous>:35:23)
at eval (eval at <anonymous> (/Users/user/Desktop/Projects/node/blog/node_modules/ejs/lib/ejs.js:242:14), <anonymous>:37:67)
at /Users/user/Desktop/Projects/node/blog/node_modules/ejs/lib/ejs.js:255:15
at Object.exports.render (/Users/user/Desktop/Projects/node/blog/node_modules/ejs/lib/ejs.js:293:13)
at View.exports.renderFile [as engine] (/Users/user/Desktop/Projects/node/blog/node_modules/ejs/lib/ejs.js:323:20)
at View.render (/Users/user/Desktop/Projects/node/blog/node_modules/express/lib/view.js:76:8)
at Function.app.render (/Users/user/Desktop/Projects/node/blog/node_modules/express/lib/application.js:517:10)
at ServerResponse.res.render (/Users/user/Desktop/Projects/node/blog/node_modules/express/lib/response.js:955:7)
at Object.res.format.html (/Users/user/Desktop/Projects/node/blog/app/routes.js:70:13)
routes.js:
var express = require('express');
var app = express();
var router = express.Router();
var blogDB = require('../config/blogDB.js');
var Blogpost = require('./models/blogModel.js');
var paginate = require('express-paginate');
//index
router.route('/')
.get(function(req, res) {
var drinks = [
{ name: 'Bloody Mary', drunkness: 3 },
{ name: 'Martini', drunkness: 5 },
{ name: 'Scotch', drunkness: 10}
];
var tagline = "Lets do this.";
res.render('pages/index', {
drinks: drinks,
tagline: tagline
});
});
app.use(paginate.middleware(10, 50));
//blog
router.route('/blog')
// START POST method
.post(function(req, res) {
var blogpost = new Blogpost(); // create a new instance of a Blogpost model
blogpost.title = req.body.title; // set the blog title
blogpost.author = req.body.author; // set the author name
blogpost.content = req.body.content; // set the blog content
blogpost.date = req.body.date; // set the date of the post
//Save Blog Post
blogpost.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'Blog created.' });
});
}) // END POST method
// START GET method
.get(function(req, res, next) {
Blogpost.paginate({}, req.query.page, req.query.limit, function(err, pageCount, blogpost, itemCount) {
if (err) return next(err)
Blogpost.find(function(err, blogpost) {
if (err)
res.send(err);
blogpost.title = req.body.title; // update the blog title
blogpost.author = req.body.author; // set the author name
blogpost.content = req.body.content; // update the blog content
blogpost.date = req.body.date; // set the date of the post
res.format({
html: function() {
res.render('pages/blog', {
blogpost: blogpost,
pageCount: pageCount,
itemCount: itemCount
})
},
json: function() {
res.json({
object: 'blogpost',
has_more: paginate.hasNextPages(req)(pageCount),
data: blogpost
})
}
}); // END res.format(html, json)
}) // END Find Blogpost
}); // END Blogpost.paginate
}); // END GET method
//Route for individual blogs
router.route('/blog/:blogpost_id')
// START GET method blog by ID
.get(function(req, res) {
Blogpost.findById(req.params.blogpost_id, function(err, blog) {
if (err)
res.send(err);
res.json(blog);
});
}) // END GET method blog by ID
// START PUT method
.put(function(req, res) {
Blogpost.findById(req.params.blogpost_id, function(err, blogpost) {
if (err)
res.send(err);
blogpost.title = req.body.title; // update the blog title
blogpost.author = req.body.author; // set the author name
blogpost.content = req.body.content; // update the blog content
blogpost.date = req.body.date; // set the date of the post
blogpost.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'Blog updated.' });
});
});
}) // END PUT method
// START DELETE method
.delete(function(req, res) {
Blogpost.remove({
_id: req.params.blogpost_id
}, function(err, bear) {
if (err)
res.send(err);
res.json({ message: 'Successfully deleted' });
});
});
//about
router.get('/about', function(req, res) {
res.render('pages/about');
});
module.exports = router;
paginate.ejs:
<div class="grid">
<div class="col-1-1">
<div class="paginate">
<ul>
<% if (paginate.hasPreiousPages) { %>
<li>
Previous
</li>
<% } %>
<% if (paginate.hasNextPages) { %>
<li>
Next
</li>
<% } %>
</ul>
</div>
</div>
</div>
blog.ejs:
<html>
<head>
<% include ../partials/head %>
</head>
<body>
<header>
<% include ../partials/header %>
</header>
<div class="grid">
<div class="col-1-1">
<div class="body-content">
<% blogpost.forEach(function(blogpost) { %>
<tr>
<td><h2><%= blogpost.title %></h2></td>
<td><h3><%= blogpost.author %></h3></td>
</tr>
<% }); %>
</div>
</div>
</div>
<div class="paginate">
<% include ../partials/paginate %>
</div>
<footer>
<% include ../partials/footer %>
</footer>
</body>
</html>
On the second line of your routes.js file, you are calling
var app = express();
This line creates a new express app.
Then you use the paginate middleware on this new app,
This is not the 'running' app, you can find the main app instance in your app.js
All your requests are handled by the app created in app.js, this way the paginare middleware never gets called.
To fix this
1: remove these two lines from routes.js
var app = express();
var paginate = require('express-paginate');
2: add in app.js (before you load your routes)
var paginate = require('express-paginate');
app.use(paginate.middleware(10, 50));
Or if you only want to use pagination for that specific set of routes,
remove
var app = express();
and use
router.use(paginate.middleware(10, 50));