Express 加 sqlite3 写一个简单博客

打印 上一主题 下一主题

主题 896|帖子 896|积分 2688

例图:



搭建 命令: 

   前提已装好node.js
  开始创建项目结构
  npm init -y
  1. package.json:
  2. {
  3.   "name": "ex01",
  4.   "version": "1.0.0",
  5.   "main": "index.js",
  6.   "scripts": {
  7.     "test": "echo "Error: no test specified" && exit 1"
  8.   },
  9.   "keywords": [],
  10.   "author": "",
  11.   "license": "ISC",
  12.   "description": ""
  13. }
复制代码
安装必要的依赖
  npm install express sqlite3 ejs express-session body-parser
  目次:


代码:

    app.js

  1. const express = require('express');
  2. const session = require('express-session');
  3. const bodyParser = require('body-parser');
  4. const path = require('path');
  5. const db = require('./database');
  6. const app = express();
  7. // 配置中间件
  8. app.set('view engine', 'ejs');
  9. app.use(express.static(path.join(__dirname, 'public')));
  10. app.use(bodyParser.urlencoded({ extended: false }));
  11. app.use(session({
  12.     secret: 'blog_secret_key',
  13.     resave: false,
  14.     saveUninitialized: true
  15. }));
  16. // 首页路由
  17. app.get('/', async (req, res) => {
  18.     try {
  19.         const category_id = req.query.category;
  20.         const search = req.query.search;
  21.         let posts;
  22.         let categories = await db.all('SELECT * FROM categories');
  23.         
  24.         if (search) {
  25.             // 搜索标题和内容
  26.             posts = await db.all(`
  27.                 SELECT posts.*, categories.name as category_name
  28.                 FROM posts
  29.                 LEFT JOIN categories ON posts.category_id = categories.id
  30.                 WHERE title LIKE ? OR content LIKE ?
  31.                 ORDER BY created_at DESC`,
  32.                 [`%${search}%`, `%${search}%`]
  33.             );
  34.         } else if (category_id) {
  35.             posts = await db.all(`
  36.                 SELECT posts.*, categories.name as category_name
  37.                 FROM posts
  38.                 LEFT JOIN categories ON posts.category_id = categories.id
  39.                 WHERE category_id = ?
  40.                 ORDER BY created_at DESC`, [category_id]);
  41.         } else {
  42.             posts = await db.all(`
  43.                 SELECT posts.*, categories.name as category_name
  44.                 FROM posts
  45.                 LEFT JOIN categories ON posts.category_id = categories.id
  46.                 ORDER BY created_at DESC`);
  47.         }
  48.         
  49.         res.render('index', {
  50.             posts,
  51.             categories,
  52.             current_category: category_id,
  53.             search_query: search || ''
  54.         });
  55.     } catch (err) {
  56.         res.status(500).send('数据库错误');
  57.     }
  58. });
  59. // 创建博文页面
  60. app.get('/post/new', async (req, res) => {
  61.     try {
  62.         const categories = await db.all('SELECT * FROM categories');
  63.         res.render('new', { categories });
  64.     } catch (err) {
  65.         res.status(500).send('获取分类失败');
  66.     }
  67. });
  68. // 提交新博文
  69. app.post('/post/new', async (req, res) => {
  70.     const { title, content, category_id } = req.body;
  71.     try {
  72.         await db.run(
  73.             'INSERT INTO posts (title, content, category_id, created_at) VALUES (?, ?, ?, ?)',
  74.             [title, content, category_id, new Date().toISOString()]
  75.         );
  76.         res.redirect('/');
  77.     } catch (err) {
  78.         res.status(500).send('创建博文失败');
  79.     }
  80. });
  81. // 查看单篇博文
  82. app.get('/post/:id', async (req, res) => {
  83.     try {
  84.         const post = await db.get(`
  85.             SELECT posts.*, categories.name as category_name
  86.             FROM posts
  87.             LEFT JOIN categories ON posts.category_id = categories.id
  88.             WHERE posts.id = ?`, [req.params.id]);
  89.         if (post) {
  90.             res.render('post', { post });
  91.         } else {
  92.             res.status(404).send('博文不存在');
  93.         }
  94.     } catch (err) {
  95.         res.status(500).send('数据库错误');
  96.     }
  97. });
  98. // 编辑博文页面
  99. app.get('/post/:id/edit', async (req, res) => {
  100.     try {
  101.         const post = await db.get('SELECT * FROM posts WHERE id = ?', [req.params.id]);
  102.         const categories = await db.all('SELECT * FROM categories');
  103.         if (post) {
  104.             res.render('edit', { post, categories });
  105.         } else {
  106.             res.status(404).send('博文不存在');
  107.         }
  108.     } catch (err) {
  109.         res.status(500).send('数据库错误');
  110.     }
  111. });
  112. // 更新博文
  113. app.post('/post/:id/edit', async (req, res) => {
  114.     const { title, content, category_id } = req.body;
  115.     try {
  116.         await db.run(
  117.             'UPDATE posts SET title = ?, content = ?, category_id = ? WHERE id = ?',
  118.             [title, content, category_id, req.params.id]
  119.         );
  120.         res.redirect(`/post/${req.params.id}`);
  121.     } catch (err) {
  122.         res.status(500).send('更新博文失败');
  123.     }
  124. });
  125. // 删除博文
  126. app.post('/post/:id/delete', async (req, res) => {
  127.     try {
  128.         await db.run('DELETE FROM posts WHERE id = ?', [req.params.id]);
  129.         res.redirect('/');
  130.     } catch (err) {
  131.         res.status(500).send('删除博文失败');
  132.     }
  133. });
  134. const PORT = process.env.PORT || 3000;
  135. app.listen(PORT, () => {
  136.     console.log(`服务器运行在 http://localhost:${PORT}`);
  137. });
复制代码

    database.js

  1. const sqlite3 = require('sqlite3').verbose();
  2. const path = require('path');
  3. // 创建数据库连接
  4. const db = new sqlite3.Database(path.join(__dirname, 'blog.db'), (err) => {
  5.     if (err) {
  6.         console.error('数据库连接失败:', err);
  7.     } else {
  8.         console.log('成功连接到数据库');
  9.         initDatabase().catch(err => {
  10.             console.error('数据库初始化失败:', err);
  11.         });
  12.     }
  13. });
  14. // 初始化数据库表
  15. async function initDatabase() {
  16.     try {
  17.         // 检查表是否存在
  18.         const tablesExist = await get(`
  19.             SELECT name FROM sqlite_master
  20.             WHERE type='table' AND (name='posts' OR name='categories')
  21.         `);
  22.         if (!tablesExist) {
  23.             console.log('首次运行,创建数据库表...');
  24.             
  25.             // 创建分类表
  26.             await run(`
  27.                 CREATE TABLE IF NOT EXISTS categories (
  28.                     id INTEGER PRIMARY KEY AUTOINCREMENT,
  29.                     name TEXT NOT NULL UNIQUE
  30.                 )
  31.             `);
  32.             console.log('分类表创建成功');
  33.             // 创建文章表
  34.             await run(`
  35.                 CREATE TABLE IF NOT EXISTS posts (
  36.                     id INTEGER PRIMARY KEY AUTOINCREMENT,
  37.                     title TEXT NOT NULL,
  38.                     content TEXT NOT NULL,
  39.                     category_id INTEGER,
  40.                     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  41.                     FOREIGN KEY (category_id) REFERENCES categories(id)
  42.                 )
  43.             `);
  44.             console.log('文章表创建成功');
  45.             // 插入默认分类
  46.             await run(`
  47.                 INSERT INTO categories (name) VALUES
  48.                 ('技术'),
  49.                 ('生活'),
  50.                 ('随笔')
  51.             `);
  52.             console.log('默认分类创建成功');
  53.         } else {
  54.             console.log('数据库表已存在,跳过初始化');
  55.         }
  56.     } catch (err) {
  57.         console.error('数据库初始化错误:', err);
  58.         throw err;
  59.     }
  60. }
  61. // Promise 包装数据库操作
  62. function run(sql, params = []) {
  63.     return new Promise((resolve, reject) => {
  64.         db.run(sql, params, function(err) {
  65.             if (err) {
  66.                 console.error('SQL执行错误:', err);
  67.                 reject(err);
  68.             } else {
  69.                 resolve(this);
  70.             }
  71.         });
  72.     });
  73. }
  74. function get(sql, params = []) {
  75.     return new Promise((resolve, reject) => {
  76.         db.get(sql, params, (err, result) => {
  77.             if (err) {
  78.                 console.error('SQL执行错误:', err);
  79.                 reject(err);
  80.             } else {
  81.                 resolve(result);
  82.             }
  83.         });
  84.     });
  85. }
  86. function all(sql, params = []) {
  87.     return new Promise((resolve, reject) => {
  88.         db.all(sql, params, (err, rows) => {
  89.             if (err) {
  90.                 console.error('SQL执行错误:', err);
  91.                 reject(err);
  92.             } else {
  93.                 resolve(rows);
  94.             }
  95.         });
  96.     });
  97. }
  98. // 关闭数据库连接
  99. process.on('SIGINT', () => {
  100.     db.close((err) => {
  101.         if (err) {
  102.             console.error('关闭数据库时出错:', err);
  103.         } else {
  104.             console.log('数据库连接已关闭');
  105.         }
  106.         process.exit(0);
  107.     });
  108. });
  109. module.exports = {
  110.     run,
  111.     get,
  112.     all
  113. };
复制代码

     views\index.ejs

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <title>我的博客</title>
  5.     <meta charset="UTF-8">
  6.     <style>
  7.         body {
  8.             font-family: Arial, sans-serif;
  9.             max-width: 800px;
  10.             margin: 0 auto;
  11.             padding: 20px;
  12.         }
  13.         .post {
  14.             margin-bottom: 20px;
  15.             padding: 15px;
  16.             border: 1px solid #ddd;
  17.             border-radius: 5px;
  18.         }
  19.         .post h2 {
  20.             margin-top: 0;
  21.         }
  22.         .post-date {
  23.             color: #666;
  24.             font-size: 0.9em;
  25.         }
  26.         .new-post-btn {
  27.             display: inline-block;
  28.             padding: 10px 20px;
  29.             background-color: #007bff;
  30.             color: white;
  31.             text-decoration: none;
  32.             border-radius: 5px;
  33.             margin-bottom: 20px;
  34.         }
  35.         .categories {
  36.             margin: 20px 0;
  37.             padding: 10px 0;
  38.             border-bottom: 1px solid #eee;
  39.         }
  40.         .category-link {
  41.             display: inline-block;
  42.             padding: 5px 10px;
  43.             margin-right: 10px;
  44.             text-decoration: none;
  45.             color: #666;
  46.             border-radius: 3px;
  47.         }
  48.         .category-link.active {
  49.             background-color: #007bff;
  50.             color: white;
  51.         }
  52.         .post-category {
  53.             display: inline-block;
  54.             padding: 3px 8px;
  55.             background-color: #e9ecef;
  56.             border-radius: 3px;
  57.             font-size: 0.9em;
  58.             margin-right: 10px;
  59.         }
  60.         .search-box {
  61.             margin: 20px 0;
  62.             display: flex;
  63.             gap: 10px;
  64.         }
  65.         .search-input {
  66.             flex: 1;
  67.             padding: 8px;
  68.             border: 1px solid #ddd;
  69.             border-radius: 4px;
  70.             font-size: 1em;
  71.         }
  72.         .search-btn {
  73.             padding: 8px 20px;
  74.             background-color: #007bff;
  75.             color: white;
  76.             border: none;
  77.             border-radius: 4px;
  78.             cursor: pointer;
  79.         }
  80.         .search-btn:hover {
  81.             background-color: #0056b3;
  82.         }
  83.         .search-results {
  84.             margin-bottom: 20px;
  85.             padding: 10px;
  86.             background-color: #f8f9fa;
  87.             border-radius: 4px;
  88.         }
  89.         .search-results-hidden {
  90.             display: none;
  91.         }
  92.     </style>
  93. </head>
  94. <body>
  95.     <h1>博客文章列表</h1>
  96.     <a href="/post/new" class="new-post-btn">写新文章</a>
  97.    
  98.     <form class="search-box" action="/" method="GET">
  99.         <input type="text" name="search" class="search-input"
  100.                placeholder="搜索文章标题或内容..."
  101.                value="<%= search_query %>">
  102.         <button type="submit" class="search-btn">搜索</button>
  103.     </form>
  104.     <div class="search-results <%= !search_query ? 'search-results-hidden' : '' %>">
  105.         搜索结果: "<%= search_query || '' %>" - 找到 <%= posts ? posts.length : 0 %> 篇文章
  106.     </div>
  107.    
  108.     <div class="categories">
  109.         <a href="/" class="category-link <%= !current_category ? 'active' : '' %>">全部</a>
  110.         <% categories.forEach(function(category) { %>
  111.             <a href="/?category=<%= category.id %>"
  112.                class="category-link <%= current_category == category.id ? 'active' : '' %>">
  113.                 <%= category.name %>
  114.             </a>
  115.         <% }); %>
  116.     </div>
  117.    
  118.     <% if (posts && posts.length > 0) { %>
  119.         <% posts.forEach(function(post) { %>
  120.             <div class="post">
  121.                 <h2><a href="/post/<%= post.id %>"><%= post.title %></a></h2>
  122.                 <div class="post-meta">
  123.                     <span class="post-category"><%= post.category_name || '未分类' %></span>
  124.                     <span class="post-date">发布时间: <%= new Date(post.created_at).toLocaleString() %></span>
  125.                 </div>
  126.                 <p><%= post.content.substring(0, 200) %>...</p>
  127.             </div>
  128.         <% }); %>
  129.     <% } else { %>
  130.         <p>还没有任何博客文章。</p>
  131.     <% } %>
  132. </body>
  133. </html>
复制代码

    views\post.ejs 

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <title><%= post.title %> - 我的博客</title>
  5.     <meta charset="UTF-8">
  6.     <style>
  7.         body {
  8.             font-family: Arial, sans-serif;
  9.             max-width: 800px;
  10.             margin: 0 auto;
  11.             padding: 20px;
  12.         }
  13.         .post-title {
  14.             margin-bottom: 10px;
  15.         }
  16.         .post-meta {
  17.             color: #666;
  18.             margin-bottom: 20px;
  19.         }
  20.         .post-content {
  21.             line-height: 1.6;
  22.             white-space: pre-wrap;
  23.         }
  24.         .back-link {
  25.             display: inline-block;
  26.             margin-bottom: 20px;
  27.             color: #007bff;
  28.             text-decoration: none;
  29.         }
  30.         .post-category {
  31.             display: inline-block;
  32.             padding: 3px 8px;
  33.             background-color: #e9ecef;
  34.             border-radius: 3px;
  35.             font-size: 0.9em;
  36.             margin-right: 10px;
  37.         }
  38.         .action-buttons {
  39.             margin: 20px 0;
  40.             display: flex;
  41.             gap: 10px;
  42.         }
  43.         .edit-btn {
  44.             padding: 5px 15px;
  45.             background-color: #28a745;
  46.             color: white;
  47.             text-decoration: none;
  48.             border-radius: 3px;
  49.             font-size: 0.9em;
  50.         }
  51.         .delete-btn {
  52.             padding: 5px 15px;
  53.             background-color: #dc3545;
  54.             color: white;
  55.             border: none;
  56.             border-radius: 3px;
  57.             cursor: pointer;
  58.             font-size: 0.9em;
  59.         }
  60.         .delete-btn:hover {
  61.             background-color: #c82333;
  62.         }
  63.     </style>
  64. </head>
  65. <body>
  66.     <a href="/" class="back-link">← 返回首页</a>
  67.    
  68.     <article>
  69.         <h1 class="post-title"><%= post.title %></h1>
  70.         <div class="post-meta">
  71.             <span class="post-category"><%= post.category_name || '未分类' %></span>
  72.             <span class="post-date">发布时间: <%= new Date(post.created_at).toLocaleString() %></span>
  73.         </div>
  74.         
  75.         <div class="action-buttons">
  76.             <a href="/post/<%= post.id %>/edit" class="edit-btn">编辑文章</a>
  77.             <form action="/post/<%= post.id %>/delete" method="POST" style="display: inline;"
  78.                   onsubmit="return confirm('确定要删除这篇文章吗?');">
  79.                 <button type="submit" class="delete-btn">删除文章</button>
  80.             </form>
  81.         </div>
  82.         
  83.         <div class="post-content">
  84.             <%= post.content %>
  85.         </div>
  86.     </article>
  87. </body>
  88. </html>
复制代码

     views\new.ejs

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <title>写新文章 - 我的博客</title>
  5.     <meta charset="UTF-8">
  6.     <style>
  7.         body {
  8.             font-family: Arial, sans-serif;
  9.             max-width: 800px;
  10.             margin: 0 auto;
  11.             padding: 20px;
  12.         }
  13.         form {
  14.             display: flex;
  15.             flex-direction: column;
  16.         }
  17.         input, textarea, select {
  18.             margin: 10px 0;
  19.             padding: 8px;
  20.             border: 1px solid #ddd;
  21.             border-radius: 4px;
  22.         }
  23.         textarea {
  24.             height: 300px;
  25.         }
  26.         button {
  27.             padding: 10px 20px;
  28.             background-color: #007bff;
  29.             color: white;
  30.             border: none;
  31.             border-radius: 5px;
  32.             cursor: pointer;
  33.         }
  34.         button:hover {
  35.             background-color: #0056b3;
  36.         }
  37.         .back-link {
  38.             display: inline-block;
  39.             margin-bottom: 20px;
  40.             color: #007bff;
  41.             text-decoration: none;
  42.         }
  43.         label {
  44.             margin-top: 10px;
  45.             color: #666;
  46.         }
  47.     </style>
  48. </head>
  49. <body>
  50.     <a href="/" class="back-link">← 返回首页</a>
  51.     <h1>写新文章</h1>
  52.    
  53.     <form action="/post/new" method="POST">
  54.         <label for="title">文章标题</label>
  55.         <input type="text" id="title" name="title" placeholder="文章标题" required>
  56.         
  57.         <label for="category">选择分类</label>
  58.         <select id="category" name="category_id" required>
  59.             <option value="">请选择分类</option>
  60.             <% categories.forEach(function(category) { %>
  61.                 <option value="<%= category.id %>"><%= category.name %></option>
  62.             <% }); %>
  63.         </select>
  64.         
  65.         <label for="content">文章内容</label>
  66.         <textarea id="content" name="content" placeholder="文章内容" required></textarea>
  67.         
  68.         <button type="submit">发布文章</button>
  69.     </form>
  70. </body>
  71. </html>
复制代码

     views\edit.ejs

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <title>编辑文章 - 我的博客</title>
  5.     <meta charset="UTF-8">
  6.     <style>
  7.         body {
  8.             font-family: Arial, sans-serif;
  9.             max-width: 800px;
  10.             margin: 0 auto;
  11.             padding: 20px;
  12.         }
  13.         form {
  14.             display: flex;
  15.             flex-direction: column;
  16.         }
  17.         input, textarea, select {
  18.             margin: 10px 0;
  19.             padding: 8px;
  20.             border: 1px solid #ddd;
  21.             border-radius: 4px;
  22.         }
  23.         textarea {
  24.             height: 300px;
  25.         }
  26.         button {
  27.             padding: 10px 20px;
  28.             background-color: #007bff;
  29.             color: white;
  30.             border: none;
  31.             border-radius: 5px;
  32.             cursor: pointer;
  33.         }
  34.         button:hover {
  35.             background-color: #0056b3;
  36.         }
  37.         .back-link {
  38.             display: inline-block;
  39.             margin-bottom: 20px;
  40.             color: #007bff;
  41.             text-decoration: none;
  42.         }
  43.         label {
  44.             margin-top: 10px;
  45.             color: #666;
  46.         }
  47.     </style>
  48. </head>
  49. <body>
  50.     <a href="/post/<%= post.id %>" class="back-link">← 返回文章</a>
  51.     <h1>编辑文章</h1>
  52.    
  53.     <form action="/post/<%= post.id %>/edit" method="POST">
  54.         <label for="title">文章标题</label>
  55.         <input type="text" id="title" name="title" value="<%= post.title %>" required>
  56.         
  57.         <label for="category">选择分类</label>
  58.         <select id="category" name="category_id" required>
  59.             <option value="">请选择分类</option>
  60.             <% categories.forEach(function(category) { %>
  61.                 <option value="<%= category.id %>" <%= post.category_id == category.id ? 'selected' : '' %>>
  62.                     <%= category.name %>
  63.                 </option>
  64.             <% }); %>
  65.         </select>
  66.         
  67.         <label for="content">文章内容</label>
  68.         <textarea id="content" name="content" required><%= post.content %></textarea>
  69.         
  70.         <button type="submit">更新文章</button>
  71.     </form>
  72. </body>
  73. </html>
复制代码
运行:

   node app.js
  服务器运行在 http://localhost:3000

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

科技颠覆者

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表