hocvietcode.com
  • Trang chủ
  • Học lập trình
    • Lập trình C/C++
    • Cấu trúc dữ liệu và giải thuật
    • Lập trình HTML
    • Lập trình Javascript
      • Javascript cơ bản
      • ReactJS framework
      • AngularJS framework
      • Typescript cơ bản
      • Angular
      • NodeJS
    • Lập trình Mobile
      • Lập Trình Dart Cơ Bản
        • Dart Flutter Framework
    • Cơ sở dữ liệu
      • MySQL – MariaDB
      • Micrsoft SQL Server
      • Extensible Markup Language (XML)
      • JSON
      • MongoDB
    • Lập trình PHP
      • Lập trình PHP cơ bản
      • Laravel Framework
    • Lập trình Java
      • Java Cơ bản
    • Lập trình C#
      • Lập Trình C# Cơ Bản
      • ASP.NET Core MVC
    • Machine Learning
    • Git và Github
  • WORDPRESS
    • WordPress cơ bản
    • WordPress nâng cao
    • Chia sẻ WordPress
  • Kiến thức hệ thống
    • Microsoft Azure
    • Docker
    • Linux
  • Chia sẻ IT
    • Tin học văn phòng
      • Microsoft Word
      • Microsoft Excel
    • Marketing
      • Google Adwords
      • Facebook Ads
      • Kiến thức khác
    • Chia sẻ phần mềm
    • Review công nghệ
    • Công cụ – tiện ích
      • Kiểm tra bàn phím online
      • Kiểm tra webcam online
Đăng nhập
  • Đăng nhập / Đăng ký

Please enter key search to display results.

Home
  • NodeJS
Hoàn thiện ứng dụng quản lý phim, xác thực và triển khai Render

Hoàn thiện ứng dụng quản lý phim, xác thực và triển khai Render

  • 24-07-2026
  • Toanngo92
  • 0 Comments

Bài 11 đã dựng bộ khung Wonderland Widescreen và các route đầu tiên. Trong bài này, chúng ta hoàn thiện ứng dụng quản lý phim: thêm, cập nhật, xóa phim, đặt ghế, đăng nhập theo vai trò, duy trì phiên làm việc và triển khai ứng dụng Node.js lên Render từ GitHub.

Giáo trình sử dụng tài khoản minh họa được viết trực tiếp trong mã nguồn. Bài viết sẽ giữ nguyên luồng chức năng nhưng thay bằng cách triển khai an toàn hơn: mật khẩu được băm, bí mật nằm trong biến môi trường và session được lưu trong MongoDB.

Mục lục

  • 1. Các chức năng cần hoàn thiện
  • 2. Cài đặt các gói xác thực và session
  • 3. Cấu hình session cho Express
  • 4. Tạo người dùng và cấu hình Passport Local
  • 5. Route đăng nhập, đăng xuất và phân quyền
  • 6. Hoàn thiện thêm, cập nhật và xóa phim
  • 7. Đặt ghế an toàn bằng thao tác nguyên tử
  • 8. Chuẩn bị ứng dụng để triển khai
  • 9. Đẩy mã nguồn lên GitHub
  • 10. Triển khai Web Service trên Render
  • 11. Danh sách kiểm tra trước khi bàn giao
  • Tổng kết

1. Các chức năng cần hoàn thiện

  • Khách đăng nhập, xem danh sách phim và đặt ghế.
  • Quản trị viên thêm, cập nhật hoặc xóa phim.
  • Ứng dụng ghi nhớ người dùng giữa các request bằng session.
  • Route được bảo vệ theo trạng thái đăng nhập và vai trò.
  • Mã nguồn được đẩy lên GitHub và triển khai thành Web Service trên Render.
Trang chủ Wonderland Widescreen cho phép chọn đăng nhập quản trị hoặc khách
Trang chủ phân luồng đăng nhập quản trị viên và khách trong giáo trình.

2. Cài đặt các gói xác thực và session

npm install passport passport-local express-session connect-mongo bcrypt dotenv

Passport chỉ phụ trách xác thực request. express-session tạo session và đặt một mã định danh trong cookie; dữ liệu phiên nằm phía máy chủ. Không dùng MemoryStore mặc định khi triển khai thật vì nó không phù hợp cho nhiều tiến trình và sẽ mất dữ liệu khi dịch vụ khởi động lại.

Tạo thêm các biến môi trường sau. Không commit tệp .env lên GitHub.

MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>/
DB_NAME=MovieCollection
SESSION_SECRET=thay-bang-chuoi-ngau-nhien-dai
PORT=3000
NODE_ENV=development

3. Cấu hình session cho Express

const session = require('express-session');
const MongoStore = require('connect-mongo');
const passport = require('passport');

if (process.env.NODE_ENV === 'production') {
  app.set('trust proxy', 1);
}

app.use(session({
  name: 'wonderland.sid',
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  store: MongoStore.create({
    mongoUrl: process.env.MONGODB_URI,
    dbName: process.env.DB_NAME
  }),
  cookie: {
    httpOnly: true,
    sameSite: 'lax',
    secure: process.env.NODE_ENV === 'production',
    maxAge: 1000 * 60 * 60 * 8
  }
}));

app.use(passport.initialize());
app.use(passport.session());

Thứ tự middleware rất quan trọng: session phải được khởi tạo trước passport.session(). Cookie chỉ chứa mã phiên, đặt httpOnly để JavaScript phía trình duyệt không đọc được và bật secure khi chạy qua HTTPS.

4. Tạo người dùng và cấu hình Passport Local

Mỗi tài khoản nên có tối thiểu username, passwordHash và role. Khi tạo tài khoản, băm mật khẩu bằng bcrypt thay vì lưu chuỗi gốc.

const bcrypt = require('bcrypt');

const admin = {
  username: 'admin',
  passwordHash: await bcrypt.hash(process.env.ADMIN_PASSWORD, 12),
  role: 'admin'
};

await users.updateOne(
  { username: admin.username },
  { $setOnInsert: admin },
  { upsert: true }
);
const { Strategy: LocalStrategy } = require('passport-local');
const { ObjectId } = require('mongodb');

passport.use(new LocalStrategy(async (username, password, done) => {
  try {
    const user = await users.findOne({ username });
    if (!user) return done(null, false, { message: 'Sai thông tin đăng nhập' });

    const valid = await bcrypt.compare(password, user.passwordHash);
    if (!valid) return done(null, false, { message: 'Sai thông tin đăng nhập' });

    return done(null, user);
  } catch (error) {
    return done(error);
  }
}));

passport.serializeUser((user, done) => done(null, user._id.toString()));

passport.deserializeUser(async (id, done) => {
  try {
    done(null, await users.findOne({ _id: new ObjectId(id) }));
  } catch (error) {
    done(error);
  }
});
Biểu mẫu đăng nhập dành cho khách của ứng dụng phim
Biểu mẫu đăng nhập khách trong giáo trình.

5. Route đăng nhập, đăng xuất và phân quyền

app.get('/login', (req, res) => res.render('login'));

app.post('/login', passport.authenticate('local', {
  failureRedirect: '/login'
}), (req, res) => {
  res.redirect(req.user.role === 'admin' ? '/admin' : '/movies');
});

app.post('/logout', (req, res, next) => {
  req.logout((error) => {
    if (error) return next(error);
    req.session.destroy(() => {
      res.clearCookie('wonderland.sid');
      res.redirect('/');
    });
  });
});
function requireLogin(req, res, next) {
  if (req.isAuthenticated()) return next();
  res.redirect('/login');
}

function requireRole(role) {
  return (req, res, next) => {
    if (!req.isAuthenticated()) return res.redirect('/login');
    if (req.user.role !== role) return res.sendStatus(403);
    next();
  };
}

app.get('/movies', requireLogin, showMovies);
app.get('/admin', requireRole('admin'), showAdminDashboard);

Ẩn nút trên giao diện không phải là phân quyền. Tất cả route thay đổi dữ liệu vẫn phải kiểm tra vai trò ở máy chủ. Nên dùng biểu mẫu POST cho đăng xuất thay vì một liên kết GET.

Bảng điều khiển quản trị và biểu mẫu thêm phim
Bảng điều khiển quản trị và trang thêm phim của Wonderland Widescreen.

6. Hoàn thiện thêm, cập nhật và xóa phim

app.post('/admin/movies', requireRole('admin'), async (req, res, next) => {
  try {
    const movie = normalizeMovie(req.body);
    await movies.insertOne(movie);
    res.redirect('/admin');
  } catch (error) {
    next(error);
  }
});

app.post('/admin/movies/:id/seats', requireRole('admin'), async (req, res, next) => {
  try {
    const availableSeats = Number(req.body.availableSeats);
    if (!Number.isInteger(availableSeats) || availableSeats < 0) {
      return res.status(400).send('Số ghế không hợp lệ');
    }

    await movies.updateOne(
      { _id: new ObjectId(req.params.id) },
      { $set: { availableSeats } }
    );
    res.redirect('/admin');
  } catch (error) {
    next(error);
  }
});

app.post('/admin/movies/:id/delete', requireRole('admin'), async (req, res, next) => {
  try {
    await movies.deleteOne({ _id: new ObjectId(req.params.id) });
    res.redirect('/admin');
  } catch (error) {
    next(error);
  }
});
Biểu mẫu xóa phim trong ứng dụng Node.js
Biểu mẫu xóa phim theo thiết kế trong giáo trình.
Kết quả cập nhật số ghế và xóa phim
Các bước cập nhật số ghế và xóa phim trong ứng dụng mẫu.

Ví dụ dùng POST để tương thích với form HTML. Trong API chuẩn có thể dùng PATCH và DELETE. Hãy thêm CSRF token cho mọi biểu mẫu thay đổi dữ liệu trước khi đưa ứng dụng vào sử dụng thực tế.

7. Đặt ghế an toàn bằng thao tác nguyên tử

Danh sách phim và biểu mẫu đặt ghế
Luồng xem danh sách phim và đặt ghế của người dùng.
app.post('/movies/:id/book', requireLogin, async (req, res, next) => {
  try {
    const seats = Number(req.body.seats);
    if (!Number.isInteger(seats) || seats < 1) {
      return res.status(400).send('Số ghế đặt không hợp lệ');
    }

    const result = await movies.updateOne(
      {
        _id: new ObjectId(req.params.id),
        availableSeats: { $gte: seats }
      },
      { $inc: { availableSeats: -seats } }
    );

    if (result.modifiedCount === 0) {
      return res.status(409).send('Không còn đủ ghế');
    }

    res.redirect('/movies');
  } catch (error) {
    next(error);
  }
});

Không nên đọc số ghế rồi cập nhật bằng hai câu lệnh tách rời vì hai người có thể đặt cùng lúc. Điều kiện availableSeats: { $gte: seats } và toán tử $inc thực hiện kiểm tra cùng cập nhật trong một thao tác nguyên tử.

8. Chuẩn bị ứng dụng để triển khai

Thêm script khởi động và yêu cầu phiên bản Node.js vào package.json:

{
  "scripts": {
    "start": "node server.js"
  },
  "engines": {
    "node": ">=20"
  }
}
const port = process.env.PORT || 3000;
app.listen(port, '0.0.0.0', () => {
  console.log(`Server đang chạy trên cổng ${port}`);
});

Tệp .gitignore tối thiểu nên loại bỏ node_modules/, .env, log và tệp hệ điều hành. Chạy ứng dụng cục bộ với NODE_ENV=production để phát hiện sớm lỗi cookie hoặc biến môi trường.

9. Đẩy mã nguồn lên GitHub

git init
git add .
git commit -m "Hoan thien Wonderland Widescreen"
git branch -M main
git remote add origin https://github.com/USERNAME/movie-application.git
git push -u origin main

Trước khi push, dùng git status và kiểm tra chắc chắn .env không nằm trong danh sách commit. Nếu bí mật từng được commit, xóa tệp ở commit mới là chưa đủ; cần thu hồi và tạo lại thông tin đăng nhập ngay.

10. Triển khai Web Service trên Render

  1. Trong Render Dashboard, chọn New → Web Service.
  2. Kết nối repository GitHub chứa ứng dụng.
  3. Chọn runtime Node, đặt Build Command là npm ci và Start Command là npm start.
  4. Thêm MONGODB_URI, DB_NAME, SESSION_SECRET, ADMIN_PASSWORD và NODE_ENV=production trong phần Environment.
  5. Triển khai, mở URL onrender.com và kiểm tra đăng nhập, CRUD, đặt ghế, đăng xuất.

Render có thể tự động build và triển khai lại khi branch đã kết nối nhận commit mới. Không đặt biến PORT cố định trên dịch vụ; mã nguồn phải đọc cổng do nền tảng cung cấp.

11. Danh sách kiểm tra trước khi bàn giao

  • Khách không truy cập được route quản trị.
  • Quản trị viên thêm, cập nhật và xóa phim thành công.
  • Không thể đặt số ghế âm, bằng 0 hoặc lớn hơn số ghế còn lại.
  • Đăng xuất hủy session và cookie.
  • Mật khẩu, MongoDB URI và session secret không xuất hiện trong repository hoặc log.
  • Trang 404 và middleware xử lý lỗi hoạt động.
  • Ứng dụng chạy đúng sau một lần redeploy hoặc restart.

Tổng kết

Wonderland Widescreen giờ đã có đầy đủ luồng người dùng và quản trị: đăng nhập theo session, phân quyền route, CRUD phim, đặt ghế an toàn và triển khai lên môi trường cloud. Đây cũng là cấu trúc nền tảng có thể áp dụng cho ứng dụng quản lý sinh viên, đặt lịch hoặc quản lý sản phẩm.

Bài viết liên quan:

Xây dựng ứng dụng quản lý phim với Express, EJS và MongoDB
Xây dựng website với Express, EJS và MongoDB
Kết nối MongoDB và xây dựng CRUD API
Xây dựng RESTful API với Express và HTTP trong nodejs
Đồng bộ, bất đồng bộ, Promise, async/await và Event Loop
Express.js, Routing, GET/POST và Middleware
Query String, gửi Email, Upload File và Local Modules trong Node.js
Built-in Modules trong Node.js: HTTP Server, URL và EventEmitter
Modules và Packages trong Node.js: CommonJS, ES Modules, npm và File System
Node.js Essentials: kiến trúc, Event Loop, REPL, npm và Global Objects
Node.js là gì? Kiến trúc client-server và cách cài đặt Node.js
Tìm Hiểu Express.js và Lập Trình Đồng Bộ – Bất Đồng Bộ Trong Node.js

THÊM BÌNH LUẬN Cancel reply

Dịch vụ thiết kế Wesbite

NỘI DUNG MỚI CẬP NHẬT

Hoàn thiện ứng dụng quản lý phim, xác thực và triển khai Render

Xây dựng ứng dụng quản lý phim với Express, EJS và MongoDB

Xây dựng website với Express, EJS và MongoDB

Đồng bộ, bất đồng bộ, Promise, async/await và Event Loop

Express.js, Routing, GET/POST và Middleware

Giới thiệu

hocvietcode.com là website chia sẻ và cập nhật tin tức công nghệ, chia sẻ kiến thức, kỹ năng. Chúng tôi rất cảm ơn và mong muốn nhận được nhiều phản hồi để có thể phục vụ quý bạn đọc tốt hơn !

Liên hệ quảng cáo: [email protected]

Kết nối với HỌC VIẾT CODE

© hocvietcode.com - Tech888 Co .Ltd since 2019

Đăng nhập

Trở thành một phần của cộng đồng của chúng tôi!
Registration complete. Please check your email.
Đăng nhập bằng google
Đăng kýBạn quên mật khẩu?

Create an account

Welcome! Register for an account
The user name or email address is not correct.
Registration confirmation will be emailed to you.
Log in Lost your password?

Reset password

Recover your password
Password reset email has been sent.
The email could not be sent. Possible reason: your host may have disabled the mail function.
A password will be e-mailed to you.
Log in Register
×