I am creating a e-commerce website where I store all the data in database as well as session store. For logged-in users, only the login credentials are stored in the session store. So, whenever the website is run for the first time, the data should be fetched from database from the data we get from session store but if i use a middleware for this purpose, it is not efficient as the middleware runs on every request. So, is there any way to solve this problem or is there any better solution to this problem which is more efficient? And also, you may wonder why I don't store data in the session store directly. So, the problem is, when I fetch data from session store, it is not returned in the form of a mongoose model so I have to call the database once. I use mongo store, express, node and ejs.
This is the middleware I use in my index file to fetch data from database in the mongoose model using the id stored in my session store during login.
app.use(async (req, res, next) => {
try {
if(req.session.userid) {
req.user = await user.findById(userid)
}
} catch(err) {
res.redirect('/' req.oldUrl '?err=UserNotFound')
}
next()
})
app.use(session({
secret: 'secret',
saveUninitialized: false,
resave: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365 * 100,
secure: false
},
store: store
}))
This is my mongoose model
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const userschema = new Schema({
Name : {
type : String,
required : true
},
Email : {
type : String,
required : true
},
Password : {
type : String,
required : true
},
IsSeller : {
type : Boolean,
defualt : false
},
Shopname : {
Type : String,
default : "-"
},
Cart : {
Items : [
{
productId : {
type : Schema.Types.ObjectId,
ref : 'product',
required : true
},
quantity : {
type : Number,
required : true
}
}
]
},
Myproducts : {
items : [
{
productId : {
type : Schema.Types.ObjectId,
ref : 'product',
required : true
},
quantity : {
type : Number,
required : true
}
}
]
},
Sdate : {
type : String,
default : "-"
}
})
module.exports = mongoose.model('user', userschema)
CodePudding user response:
You can use express-sessions
directly in one of your controllers if you don't want to use it as a general middleware. If you set a session cookie when you log in and only check it next time the user logs in, make sure the user is logged out when tab or browser is closed, or after a certain time if you want to keep it more flexible and let users bypass the login form for x amount of time.
// in app.js
const session = require('express-sessions')
const MongoStore = require('connect-mongodb-session')(session)
const store = new MongoStore({
uri: MONGO_URI
collection: 'sessions'
app.use(session, {
secret: 'myL0ngSecretXXX',
resave: false,
saveUninitialized: false,
store
})
// in your login controller
exports.setSession = (req, res, next) => {
req.session.isAuthenticated = true //this saves a session cookie client side
res.redirect('/dashboard')
}
// Router:
router.post('/login', loginService)
// Login service
const { setSession } = require('./controllers/login')
// other requires ...
exports.loginService = catchAsync(async (req, res, next) => {
const allowedFields = ['email', 'password']
if (!checkFieldsStrict(req.body, allowedFields) || (isEmptyRequest(req.body))) {
return next(new ErrorResponse(`Invalid request.`, 400))
}
const { email, password } = req.body
if (!email || !password) {
return next(new ErrorResponse(`Please provide your email and password.`, 400))
}
const user = await User.findOne({
where: { email },
})
if (!user) {
// Don't reveal that the user is unknown:
return next(new ErrorResponse(`Invalid credentials.`, 401))
}
const passwordMatch = await comparePasswords(password, user.password)
if (!passwordMatch) {
return next(new ErrorResponse(`Invalid credentials.`, 401))
}
setSession()
// maybe send back a token too in addition to the session?
})
CodePudding user response:
First off, you can make the middleware a lot more efficient by not rerunning it on every request. Just save the user in the session itself. Presumably, the relationship between userId
and user never changes so whenever you first get the userId
, just get the user
and save it in the session itself too. Then, you're only querying the database once per session.
app.use(async (req, res, next) => {
try {
if(req.session.userid && !req.session.user) {
req.session.user = await user.findById(userid)
}
} catch(err) {
res.redirect('/' req.oldUrl '?err=UserNotFound')
return;
}
next()
});
If you absolutely need the data in req.user
instead of req.session.user
, you can have the middleware set that too:
app.use(async (req, res, next) => {
try {
if(req.session.userid && !req.session.user) {
req.session.user = await user.findById(userid)
}
} catch(err) {
res.redirect('/' req.oldUrl '?err=UserNotFound')
return;
}
// populate req.user if user is available
if (req.session.user) {
req.user = req.session.user;
}
next()
});
If some data within the user
object might change over time and you want a fresh piece of that specific information, then you can put a DB request to get the absolute latest user object in those specific routes. That way you are only hitting the database when the user initially signs in and when a route absolutely needs the latest info from the database, rather than on every single request.
Oh, one other thing. After you call res.redirect()
, you need to return
from the middleware and not call next()
because you're done processing that request and don't want further route handling on that request.