Express 加 sqlite3 写一个简单博客

例图:

搭建 命令: 

前提已装好node.js

开始创建项目结构

npm init -y

package.json:

{
  "name": "ex01",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}

安装必要的依赖

npm install express sqlite3 ejs express-session body-parser

目录:

代码:

 app.js

const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');
const path = require('path');
const db = require('./database');

const app = express();

// 配置中间件
app.set('view engine', 'ejs');
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({
    secret: 'blog_secret_key',
    resave: false,
    saveUninitialized: true
}));

// 首页路由
app.get('/', async (req, res) => {
    try {
        const category_id = req.query.category;
        const search = req.query.search;
        let posts;
        let categories = await db.all('SELECT * FROM categories');
        
        if (search) {
            // 搜索标题和内容
            posts = await db.all(`
                SELECT posts.*, categories.name as category_name 
                FROM posts 
                LEFT JOIN categories ON posts.category_id = categories.id 
                WHERE title LIKE ? OR content LIKE ?
                ORDER BY created_at DESC`, 
                [`%${search}%`, `%${search}%`]
            );
        } else if (category_id) {
            posts = await db.all(`
                SELECT posts.*, categories.name as category_name 
                FROM posts 
                LEFT JOIN categories ON posts.category_id = categories.id 
                WHERE category_id = ? 
                ORDER BY created_at DESC`, [category_id]);
        } else {
            posts = await db.all(`
                SELECT posts.*, categories.name as category_name 
                FROM posts 
                LEFT JOIN categories ON posts.category_id = categories.id 
                ORDER BY created_at DESC`);
        }
        
        res.render('index', { 
            posts, 
            categories, 
            current_category: category_id,
            search_query: search || ''
        });
    } catch (err) {
        res.status(500).send('数据库错误');
    }
});

// 创建博文页面
app.get('/post/new', async (req, res) => {
    try {
        const categories = await db.all('SELECT * FROM categories');
        res.render('new', { categories });
    } catch (err) {
        res.status(500).send('获取分类失败');
    }
});

// 提交新博文
app.post('/post/new', async (req, res) => {
    const { title, content, category_id } = req.body;
    try {
        await db.run(
            'INSERT INTO posts (title, content, category_id, created_at) VALUES (?, ?, ?, ?)',
            [title, content, category_id, new Date().toISOString()]
        );
        res.redirect('/');
    } catch (err) {
        res.status(500).send('创建博文失败');
    }
});

// 查看单篇博文
app.get('/post/:id', async (req, res) => {
    try {
        const post = await db.get(`
            SELECT posts.*, categories.name as category_name 
            FROM posts 
            LEFT JOIN categories ON posts.category_id = categories.id 
            WHERE posts.id = ?`, [req.params.id]);
        if (post) {
            res.render('post', { post });
        } else {
            res.status(404).send('博文不存在');
        }
    } catch (err) {
        res.status(500).send('数据库错误');
    }
});

// 编辑博文页面
app.get('/post/:id/edit', async (req, res) => {
    try {
        const post = await db.get('SELECT * FROM posts WHERE id = ?', [req.params.id]);
        const categories = await db.all('SELECT * FROM categories');
        if (post) {
            res.render('edit', { post, categories });
        } else {
            res.status(404).send('博文不存在');
        }
    } catch (err) {
        res.status(500).send('数据库错误');
    }
});

// 更新博文
app.post('/post/:id/edit', async (req, res) => {
    const { title, content, category_id } = req.body;
    try {
        await db.run(
            'UPDATE posts SET title = ?, content = ?, category_id = ? WHERE id = ?',
            [title, content, category_id, req.params.id]
        );
        res.redirect(`/post/${req.params.id}`);
    } catch (err) {
        res.status(500).send('更新博文失败');
    }
});

// 删除博文
app.post('/post/:id/delete', async (req, res) => {
    try {
        await db.run('DELETE FROM posts WHERE id = ?', [req.params.id]);
        res.redirect('/');
    } catch (err) {
        res.status(500).send('删除博文失败');
    }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`服务器运行在 http://localhost:${PORT}`);
}); 

database.js

const sqlite3 = require('sqlite3').verbose();
const path = require('path');

// 创建数据库连接
const db = new sqlite3.Database(path.join(__dirname, 'blog.db'), (err) => {
    if (err) {
        console.error('数据库连接失败:', err);
    } else {
        console.log('成功连接到数据库');
        initDatabase().catch(err => {
            console.error('数据库初始化失败:', err);
        });
    }
});

// 初始化数据库表
async function initDatabase() {
    try {
        // 检查表是否存在
        const tablesExist = await get(`
            SELECT name FROM sqlite_master 
            WHERE type='table' AND (name='posts' OR name='categories')
        `);

        if (!tablesExist) {
            console.log('首次运行,创建数据库表...');
            
            // 创建分类表
            await run(`
                CREATE TABLE IF NOT EXISTS categories (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT NOT NULL UNIQUE
                )
            `);
            console.log('分类表创建成功');

            // 创建文章表
            await run(`
                CREATE TABLE IF NOT EXISTS posts (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    title TEXT NOT NULL,
                    content TEXT NOT NULL,
                    category_id INTEGER,
                    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
                    FOREIGN KEY (category_id) REFERENCES categories(id)
                )
            `);
            console.log('文章表创建成功');

            // 插入默认分类
            await run(`
                INSERT INTO categories (name) VALUES 
                ('技术'),
                ('生活'),
                ('随笔')
            `);
            console.log('默认分类创建成功');
        } else {
            console.log('数据库表已存在,跳过初始化');
        }
    } catch (err) {
        console.error('数据库初始化错误:', err);
        throw err;
    }
}

// Promise 包装数据库操作
function run(sql, params = []) {
    return new Promise((resolve, reject) => {
        db.run(sql, params, function(err) {
            if (err) {
                console.error('SQL执行错误:', err);
                reject(err);
            } else {
                resolve(this);
            }
        });
    });
}

function get(sql, params = []) {
    return new Promise((resolve, reject) => {
        db.get(sql, params, (err, result) => {
            if (err) {
                console.error('SQL执行错误:', err);
                reject(err);
            } else {
                resolve(result);
            }
        });
    });
}

function all(sql, params = []) {
    return new Promise((resolve, reject) => {
        db.all(sql, params, (err, rows) => {
            if (err) {
                console.error('SQL执行错误:', err);
                reject(err);
            } else {
                resolve(rows);
            }
        });
    });
}

// 关闭数据库连接
process.on('SIGINT', () => {
    db.close((err) => {
        if (err) {
            console.error('关闭数据库时出错:', err);
        } else {
            console.log('数据库连接已关闭');
        }
        process.exit(0);
    });
});

module.exports = {
    run,
    get,
    all
}; 

 views\index.ejs

<!DOCTYPE html>
<html>
<head>
    <title>我的博客</title>
    <meta charset="UTF-8">
    <style>
        body { 
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        .post {
            margin-bottom: 20px;
            padding: 15px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        .post h2 {
            margin-top: 0;
        }
        .post-date {
            color: #666;
            font-size: 0.9em;
        }
        .new-post-btn {
            display: inline-block;
            padding: 10px 20px;
            background-color: #007bff;
            color: white;
            text-decoration: none;
            border-radius: 5px;
            margin-bottom: 20px;
        }
        .categories {
            margin: 20px 0;
            padding: 10px 0;
            border-bottom: 1px solid #eee;
        }
        .category-link {
            display: inline-block;
            padding: 5px 10px;
            margin-right: 10px;
            text-decoration: none;
            color: #666;
            border-radius: 3px;
        }
        .category-link.active {
            background-color: #007bff;
            color: white;
        }
        .post-category {
            display: inline-block;
            padding: 3px 8px;
            background-color: #e9ecef;
            border-radius: 3px;
            font-size: 0.9em;
            margin-right: 10px;
        }
        .search-box {
            margin: 20px 0;
            display: flex;
            gap: 10px;
        }
        .search-input {
            flex: 1;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 1em;
        }
        .search-btn {
            padding: 8px 20px;
            background-color: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        .search-btn:hover {
            background-color: #0056b3;
        }
        .search-results {
            margin-bottom: 20px;
            padding: 10px;
            background-color: #f8f9fa;
            border-radius: 4px;
        }
        .search-results-hidden {
            display: none;
        }
    </style>
</head>
<body>
    <h1>博客文章列表</h1>
    <a href="/post/new" class="new-post-btn">写新文章</a>
    
    <form class="search-box" action="/" method="GET">
        <input type="text" name="search" class="search-input" 
               placeholder="搜索文章标题或内容..." 
               value="<%= search_query %>">
        <button type="submit" class="search-btn">搜索</button>
    </form>

    <div class="search-results <%= !search_query ? 'search-results-hidden' : '' %>">
        搜索结果: "<%= search_query || '' %>" - 找到 <%= posts ? posts.length : 0 %> 篇文章
    </div>
    
    <div class="categories">
        <a href="/" class="category-link <%= !current_category ? 'active' : '' %>">全部</a>
        <% categories.forEach(function(category) { %>
            <a href="/?category=<%= category.id %>" 
               class="category-link <%= current_category == category.id ? 'active' : '' %>">
                <%= category.name %>
            </a>
        <% }); %>
    </div>
    
    <% if (posts && posts.length > 0) { %>
        <% posts.forEach(function(post) { %>
            <div class="post">
                <h2><a href="/post/<%= post.id %>"><%= post.title %></a></h2>
                <div class="post-meta">
                    <span class="post-category"><%= post.category_name || '未分类' %></span>
                    <span class="post-date">发布时间: <%= new Date(post.created_at).toLocaleString() %></span>
                </div>
                <p><%= post.content.substring(0, 200) %>...</p>
            </div>
        <% }); %>
    <% } else { %>
        <p>还没有任何博客文章。</p>
    <% } %>
</body>
</html> 

views\post.ejs 

<!DOCTYPE html>
<html>
<head>
    <title><%= post.title %> - 我的博客</title>
    <meta charset="UTF-8">
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        .post-title {
            margin-bottom: 10px;
        }
        .post-meta {
            color: #666;
            margin-bottom: 20px;
        }
        .post-content {
            line-height: 1.6;
            white-space: pre-wrap;
        }
        .back-link {
            display: inline-block;
            margin-bottom: 20px;
            color: #007bff;
            text-decoration: none;
        }
        .post-category {
            display: inline-block;
            padding: 3px 8px;
            background-color: #e9ecef;
            border-radius: 3px;
            font-size: 0.9em;
            margin-right: 10px;
        }
        .action-buttons {
            margin: 20px 0;
            display: flex;
            gap: 10px;
        }
        .edit-btn {
            padding: 5px 15px;
            background-color: #28a745;
            color: white;
            text-decoration: none;
            border-radius: 3px;
            font-size: 0.9em;
        }
        .delete-btn {
            padding: 5px 15px;
            background-color: #dc3545;
            color: white;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            font-size: 0.9em;
        }
        .delete-btn:hover {
            background-color: #c82333;
        }
    </style>
</head>
<body>
    <a href="/" class="back-link">← 返回首页</a>
    
    <article>
        <h1 class="post-title"><%= post.title %></h1>
        <div class="post-meta">
            <span class="post-category"><%= post.category_name || '未分类' %></span>
            <span class="post-date">发布时间: <%= new Date(post.created_at).toLocaleString() %></span>
        </div>
        
        <div class="action-buttons">
            <a href="/post/<%= post.id %>/edit" class="edit-btn">编辑文章</a>
            <form action="/post/<%= post.id %>/delete" method="POST" style="display: inline;" 
                  onsubmit="return confirm('确定要删除这篇文章吗?');">
                <button type="submit" class="delete-btn">删除文章</button>
            </form>
        </div>
        
        <div class="post-content">
            <%= post.content %>
        </div>
    </article>
</body>
</html> 

 views\new.ejs

<!DOCTYPE html>
<html>
<head>
    <title>写新文章 - 我的博客</title>
    <meta charset="UTF-8">
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        form {
            display: flex;
            flex-direction: column;
        }
        input, textarea, select {
            margin: 10px 0;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        textarea {
            height: 300px;
        }
        button {
            padding: 10px 20px;
            background-color: #007bff;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        button:hover {
            background-color: #0056b3;
        }
        .back-link {
            display: inline-block;
            margin-bottom: 20px;
            color: #007bff;
            text-decoration: none;
        }
        label {
            margin-top: 10px;
            color: #666;
        }
    </style>
</head>
<body>
    <a href="/" class="back-link">← 返回首页</a>
    <h1>写新文章</h1>
    
    <form action="/post/new" method="POST">
        <label for="title">文章标题</label>
        <input type="text" id="title" name="title" placeholder="文章标题" required>
        
        <label for="category">选择分类</label>
        <select id="category" name="category_id" required>
            <option value="">请选择分类</option>
            <% categories.forEach(function(category) { %>
                <option value="<%= category.id %>"><%= category.name %></option>
            <% }); %>
        </select>
        
        <label for="content">文章内容</label>
        <textarea id="content" name="content" placeholder="文章内容" required></textarea>
        
        <button type="submit">发布文章</button>
    </form>
</body>
</html> 

 views\edit.ejs

<!DOCTYPE html>
<html>
<head>
    <title>编辑文章 - 我的博客</title>
    <meta charset="UTF-8">
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        form {
            display: flex;
            flex-direction: column;
        }
        input, textarea, select {
            margin: 10px 0;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        textarea {
            height: 300px;
        }
        button {
            padding: 10px 20px;
            background-color: #007bff;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        button:hover {
            background-color: #0056b3;
        }
        .back-link {
            display: inline-block;
            margin-bottom: 20px;
            color: #007bff;
            text-decoration: none;
        }
        label {
            margin-top: 10px;
            color: #666;
        }
    </style>
</head>
<body>
    <a href="/post/<%= post.id %>" class="back-link">← 返回文章</a>
    <h1>编辑文章</h1>
    
    <form action="/post/<%= post.id %>/edit" method="POST">
        <label for="title">文章标题</label>
        <input type="text" id="title" name="title" value="<%= post.title %>" required>
        
        <label for="category">选择分类</label>
        <select id="category" name="category_id" required>
            <option value="">请选择分类</option>
            <% categories.forEach(function(category) { %>
                <option value="<%= category.id %>" <%= post.category_id == category.id ? 'selected' : '' %>>
                    <%= category.name %>
                </option>
            <% }); %>
        </select>
        
        <label for="content">文章内容</label>
        <textarea id="content" name="content" required><%= post.content %></textarea>
        
        <button type="submit">更新文章</button>
    </form>
</body>
</html> 

运行:

node app.js

服务器运行在 http://localhost:3000

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/950195.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++:字符数组

一、字符数组介绍 数组的元素如果是字符类型&#xff0c;这种数组就是字符数组&#xff0c;字符数组可以是一维数组&#xff0c;可以是二维数组 &#xff08;多维数组&#xff09;。我们接下来主要讨论的是一维的字符数组。 char arr1[5]; //⼀维字符数组 char arr2[3][5];//⼆…

基于SpringBoot实现的保障性住房管理系统

&#x1f942;(❁◡❁)您的点赞&#x1f44d;➕评论&#x1f4dd;➕收藏⭐是作者创作的最大动力&#x1f91e; &#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;欢迎留言讨论 &#x1f525;&#x1f525;&…

分享3个国内使用正版GPT的网站【亲测有效!2025最新】

1. molica 传送入口&#xff1a;https://ai-to.cn/url/?umolica 2. 多帮AI 传送入口&#xff1a;https://aigc.openaicloud.cn?inVitecodeMYAAGGKXVK 3. 厉害猫 传送入口&#xff1a;https://ai-to.cn/url/?ulihaimao

LabVIEW瞬变电磁接收系统

利用LabVIEW软件与USB4432采集卡开发瞬变电磁接收系统。系统通过改进硬件配置与软件编程&#xff0c;解决了传统仪器在信噪比低和抗干扰能力差的问题&#xff0c;实现了高精度的数据采集和处理&#xff0c;特别适用于地质勘探等领域。 ​ 项目背景&#xff1a; 瞬变电磁法是探…

CM3/4启动流程

CM3/4启动流程 1. 启动模式2. 启动流程 1. 启动模式 复位方式有三种&#xff1a;上电复位&#xff0c;硬件复位和软件复位。 当产生复位&#xff0c;并且离开复位状态后&#xff0c;CM3/4 内核做的第一件事就是读取下列两个 32 位整数的值&#xff1a; 从地址 0x0000 0000 处取…

快手短剧播放器uniapp如何引入与对接?

uniApp前端微短剧项目开源分享 开源地址&#xff1a;git开源下载地址 文章目录 快手短剧播放器uniapp如何引入与对接&#xff1f;1.引入短剧播放器2.创建文件kscomponents组件3.local-stream.js文件说明4.用户行为事件4.local-stream.ksml文件参考如下 快手短剧播放器uniapp如何…

.NET AI 开发人员库 --AI Dev Gallery简单示例--问答机器人

资源及介绍接上篇 nuget引用以下组件 效果展示&#xff1a; 内存和cpu占有&#xff1a; 代码如下&#xff1a;路径换成自己的模型路径 模型请从上篇文尾下载 internal class Program{private static CancellationTokenSource? cts;private static IChatClient? model;privat…

如何构建多层决策树

构建一颗多层的决策树时&#xff0c;通过递归选择最佳划分特征&#xff08;依据 信息增益 或 基尼系数&#xff09;对数据集进行划分&#xff0c;直到满足停止条件&#xff08;例如叶节点纯度达到要求或树的深度限制&#xff09;。以下是基于 信息增益 和 基尼系数 的递推公式和…

VSCode 使用鼠标滚轮控制字体

一、 文件 | 首选项 | 设置 二、单击在 settings.json中编辑 "editor.mouseWheelZoom": true 注注注意&#xff1a;保存哦&#xff01;ctrlS 三、测试 按住ctrl鼠标滚轮&#xff0c;控制字体大小

十年后LabVIEW编程知识是否会过时?

在考虑LabVIEW编程知识在未来十年内的有效性时&#xff0c;我们可以从几个角度进行分析&#xff1a; ​ 1. 技术发展与软件更新 随着技术的快速发展&#xff0c;许多编程工具和平台不断更新和改进&#xff0c;LabVIEW也不例外。十年后&#xff0c;可能会有新的编程语言或平台…

注册中心如何选型?Eureka、Zookeeper、Nacos怎么选

这是小卷对分布式系统架构学习的第9篇文章&#xff0c;第8篇时只回答了注册中心的工作原理的内容&#xff0c;面试官的第二个问题还没回答&#xff0c;今天再来讲讲各个注册中心的原理&#xff0c;以及区别&#xff0c;最后如何进行选型 上一篇文章&#xff1a;如何设计一个注册…

C++ 复习总结记录三

C 复习总结记录三 主要内容 1、类的六个默认成员函数 2、构造函数 3、析构函数 4、拷贝构造函数 5、赋值运算符重载 6、const 成员函数 7、取地址及 const 取地址操作符重载 一 类的六个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。空类中并不是…

【简博士统计学习方法】第1章:4. 模型的评估与选择

4. 模型的评估与选择 4.1 训练误差与测试误差 假如存在样本容量为 N N N的训练集&#xff0c;将训练集送入学习系统可以训练学习得到一个模型&#xff0c;我们将这么模型用决策函数的形式表达&#xff0c;也就是 y f ^ ( x ) y\hat{f}(x) yf^​(x)&#xff0c;关于模型的拟合…

计算机网络 (30)多协议标签交换MPLS

前言 多协议标签交换&#xff08;Multi-Protocol Label Switching&#xff0c;MPLS&#xff09;是一种在开放的通信网上利用标签引导数据高速、高效传输的新技术。 一、基本概念 MPLS是一种第三代网络架构技术&#xff0c;旨在提供高速、可靠的IP骨干网络交换。它通过将IP地址映…

【Java】JVM内存相关笔记

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途&#xff0c;以及创建和销毁的时间&#xff0c;有的区域随着虚拟机进程的启动而一直存在&#xff0c;有些区域则是依赖用户线程的启动和结束而建立和销毁。 程序计数器&am…

鸿蒙 ArkUI实现地图找房效果

常用的地图找房功能&#xff0c;是在地图上添加区域、商圈、房源等一些自定义 marker&#xff0c;然后配上自己应用的一些筛选逻辑构成&#xff0c;在这里使用鸿蒙 ArkUI 简单实现下怎么添加区域/商圈、房源等 Marker. 1、开启地图服务 在华为开发者官网&#xff0c;注册应用&…

STM32-WWDG/IWDG看门狗

WWDG/IWDG一旦开启不能关闭&#xff0c;可通过选项字节在上电时启动硬件看门狗&#xff0c;看门狗计数只能写入不能读取。看门狗启用时&#xff0c;T6bit必须置1&#xff0c;防止立即重置。 一、原理 独立看门狗-超时复位 窗口看门狗-喂狗&#xff08;重置计数器&#xff0c;…

基于JAVA+SSM的车辆运输管理

基于JAVASSM的车辆运输管理 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末附源码下载链接&#x1f345; 哈喽兄弟们&#…

【Linux系列】Vim 编辑器中的高效文本编辑技巧:删除操作

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

win10 VS2019上libtorch库配置过程

win10 VS2019上libtorch库配置过程 0 引言1 获取libtorch2 在VS上配置使用libtorch库3 结语 0 引言 &#x1f4bb;&#x1f4bb;AI一下&#x1f4bb;&#x1f4bb;   libtorch库是一个用于深度学习的C库&#xff0c;是PyTorch的官方C前端。它提供了用于构建和训练深度学习模…