You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Express.js登出路由无法重定向/渲染的原因及修复方案

Express.js 登出路由失效问题

我的Express.js项目中登出路由失效,即使切换至其他路由也无法完成渲染或重定向,但代码中的console.log("am clicked");能够正常执行。

相关代码:

const express = require('express')
const app = express()
const path = require('path')
const fs = require('fs')
const port = 7000
const sqlite3 = require("sqlite3").verbose();
const session = require('express-session');
const cookieParser = require('cookie-parser');
const bodyparser = require('body-parser');

let fdata = require('./fashion.json');
fdata = fdata.Sheet1;


app.use(session({
  secret: 'keyboardwarrior',
  resave: false,
  saveUninitialized: true,
  cookie: {
      maxAge: 30 * 60 * 1000 // 30 minutes
  }
}));


app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "views"));
app.use(express.static(path.join(__dirname, "public")));
app.use(express.urlencoded({ extended: false }));
app.use(bodyparser.json());
app.use(cookieParser());

app.get('/', (req, res) => {
  res.render('shop');
})

app.get('/shop', (req, res) => {
  res.render('shop');
})

app.get('/checkout', (req, res) => {
  res.render('checkout')
})

app.get('/fashion', (req, res) => {
res.send(fdata)
})

// Loggedin
app.get('/loggedin', (req, res) => {
  res.render("shop")
})

// Blank
app.get('/blank', (req, res) => {
  res.render("blank")
})


// Register
app.post('/register', async (req, res) => {
  const {firstname, lastname, usermail, password} = req.body; 
  if (!usermail || !password) return res.status(400).send({error: "Email or password missing."});

  db.serialize(() => {
    db.each('SELECT COUNT(*) as count FROM user WHERE email = ?', [usermail], (err, row) => {
      console.log(row)
      if(err) {
        console.log(err.message);
      } else if(row.count > 0) {
        console.log('there is a match');
      } else {
        db.run('INSERT INTO user (fname, lname, email, pass) VALUES(?,?,?,?)', [firstname, lastname, usermail, password], (err) => {
          if (err) {
            return console.log(err.message);
          }
          console.log("User successfully registered.");
        })
      
        res.redirect('/')
      } 
    }); 
  
  });


})


// const isLoggedIn = (req, res, next) => {
//   if(req.session.currentUser){
//       next();
//   }else{
//       res.redirect("/");
//   }
// };

// app.get("/", isLoggedIn, (req, res) => {
//   res.render("/", {currentUser: req.session.currentUser});
// });


app.get('/user/:id', function(req,res){
  const id = req.params.id;

  db.serialize(()=>{
    db.each('SELECT * FROM user WHERE _id = ?', [id], (err, row) => {     
      
      try {
        console.log(`${row._id}`);
        res.send(row);
      }catch (err) { 
        res.status(400).send(err);
      }
    
    })
  })
});


// login
app.post('/', async (req, res) => {
  const {usermail, password} = req.body; 

  db.serialize(()=>{
    db.each('SELECT _id ID, email USEREMAIL, pass USERPASS FROM user WHERE email = ?', [usermail], (err, row) => {     
      if(err){
        return console.error(err.message);
      }else {
       
        try {
          if(row.USEREMAIL && row.USEREMAIL === usermail) {
            console.log(`${row.ID}`);
            req.session.isLoggedIn = true;
            req.session.currentUser = `${row.USEREMAIL}`;
            res.render('shop', {uid: `${row.ID}`});
          }
        } catch (err) { 
            res.status(400).send(err);
        }

      } 
    })
  })
  
})

app.post('/logout', (req, res) => {
console.log("am clicked");
  if (req.session) {
    req.session.destroy();
    console.log('have been clicked');
  }
  return res.render('shop', {uid: ``});
});

const db = new sqlite3.Database('users.db', err => {
  if (err) {
    return console.error(err.message);
  }
  console.log("Successful connection to the database 'users.db'");
});

app.all('*', (req, res) => {
  res.render('404');
})

app.listen(port, () => {
  console.log(`Listening on port ${port}`)
})

预期效果:执行登出操作后重新渲染首页路由。


问题分析与修复方案

核心问题

  1. req.session.destroy()是异步操作:直接在调用它后执行res.render,会话还未完全销毁,后续渲染可能仍携带旧会话信息。
  2. 直接渲染而非重定向:res.render不会触发浏览器路由刷新,前端可能保留登录状态DOM,导致视觉上无变化。

修复后的登出路由代码

app.post('/logout', (req, res) => {
  console.log("am clicked");
  if (req.session) {
    // 等待会话销毁完成后再执行重定向
    req.session.destroy((err) => {
      if (err) {
        console.error('销毁会话失败:', err);
        return res.status(500).send('登出失败');
      }
      console.log('have been clicked');
      // 重定向到首页,强制浏览器刷新页面
      res.redirect('/');
    });
  } else {
    // 无会话直接重定向
    res.redirect('/');
  }
});

额外优化建议

  • 合并重复路由:/和/shop逻辑一致,可合并减少冗余:
    app.get(['/', '/shop'], (req, res) => {
      // 传递用户状态给模板,方便前端控制登录/登出按钮展示
      res.render('shop', { uid: req.session.currentUser ? req.session.currentUser.id : '' });
    });
    
  • 完善登录逻辑:补充用户不存在的判断,避免报错:
    app.post('/', async (req, res) => {
      const { usermail, password } = req.body; 
    
      db.serialize(() => {
        db.get('SELECT _id ID, email USEREMAIL, pass USERPASS FROM user WHERE email = ?', [usermail], (err, row) => {     
          if (err) {
            return console.error(err.message);
          }
          // 判断用户是否存在
          if (!row) {
            return res.status(401).send('邮箱或密码错误');
          }
          // 这里建议用bcrypt处理密码,不要明文存储/比较
          if (row.USEREMAIL === usermail && row.USERPASS === password) {
            req.session.isLoggedIn = true;
            req.session.currentUser = { email: row.USEREMAIL, id: row.ID };
            res.redirect('/');
          } else {
            res.status(401).send('邮箱或密码错误');
          }
        })
      })
    })
    
  • 密码安全优化:当前明文存储密码风险极高,建议使用bcrypt对密码哈希后存储,登录时对比哈希值。

内容的提问来源于stack exchange,提问作者wokoma osborn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 13:14:56