Express 加 sqlite3 写一个简单博客

news2025/1/10 12:46:13

例图:

搭建 命令: 

前提已装好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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GetMaterialApp组件的功能与用法

文章目录 1. 知识回顾2. 使用方法2.1 源码分析2.2 常用属性3. 示例代码4. 内容总结我们在上一章回中介绍了"Get包简介"相关的内容,本章回中将介绍GetMaterialApp组件.闲话休提,让我们一起Talk Flutter吧。 1. 知识回顾 我们在上一章回中已经介绍过GetMaterialApp组…

LabVIEW之树形控件

一、树形控件基本构成 树形控件这个名称非常形象&#xff0c;其如同树一样&#xff0c;是典型的分层结构。树形控件的属性和方法使用非常灵活&#xff0c;树形控件的内容既可以静态编辑&#xff0c;也可以通过编程来动态填充。静态编辑树形控件适用于内容不变的应用场景&#…

Inno Setup制作安装包,安装给win加环境变量

加 ; 加环境变量&#xff0c;开启&#xff0c;下面一行 ChangesEnvironmentyes 和 ; 加环境变量wbrj变量名&#xff0c;{app}\project\bin变量值&#xff0c;{app}\后接文件名&#xff0c;{app}表示安装路径。下面一行,{olddata};原来的值上拼接 Root: HKLM; Subkey: “SYSTEM\…

张朝阳惊现CES展,为中国品牌 “代言”的同时,或将布局搜狐新战略!

每年年初&#xff0c;科技圈的目光都会聚焦在美国拉斯维加斯&#xff0c;因为这里将上演一场被誉为 “科技春晚” 的年度大戏 ——CES 国际消费电子展。作为全球规模最大、最具影响力的科技展会之一&#xff0c;CES 吸引了来自 160 多个国家的创新者和行业领导者&#xff0c;是…

UDS诊断之0x27服务—结合实例讲解

前言&#xff1a; 本文讲解的是比较深入一点知识&#xff0c;对于一些刚入门的同学&#xff0c;建议直接先看一遍14229规范&#xff0c;然后找一个实际项目练练手&#xff01;然后再来看本文&#xff0c;相信你会对0x27服务有更深的认知&#xff01;&#xff01;&#xff01; …

React Router 向路由组件传state参数浏览器回退历史页面显示效果问题

昨天在看尚硅谷张天禹老师讲的 React教程p90&#xff0c;老师讲到 React路由的 replace模式和push模式&#xff0c;老师的演示效果与自己本地操作不太一样。 老师的效果&#xff1a;点击查看消息1&#xff0c;消息2&#xff0c;消息3 再点回退&#xff0c;可以依次查看到 消息…

静态路由配置与调试——计算机网络实训day1

文章目录 操作前准备一、实验目的二、实验要求三、实验过程1、在R1和R2上配置设备名称。基本配置设备命名 2、在R1和R2上配置接口IP地址&#xff0c;并查看IP地址的配置情况。3、在R1和R2上配置静态路由&#xff0c;并查看路由表。静态路由缺省路由&#xff08;默认路由&#x…

【HeadFirst系列之HeadFirst设计模式】第1天之HeadFirst设计模式开胃菜

HeadFirst设计模式开胃菜 前言 从今日起&#xff0c;陆续分享《HeadFirst设计模式》的读书笔记&#xff0c;希望能够帮助大家更好的理解设计模式&#xff0c;提高自己的编程能力。 今天要分享的是【HeadFirst设计模式开胃菜】&#xff0c;主要介绍了设计模式的基本概念、设计模…

UOS系统和windows系统wps文档显示差异问题解决

最近在使用UOS系统的过程中&#xff0c;发现了一个很有意思的现象。就是在UOS系统上编辑的文档&#xff0c;发到windows系统上&#xff0c;会出现两个文档显示差异很大的情况&#xff0c;文档都是使用一样的wps软件打开的。到底是什么原因导致这种现象的呢&#xff1f;该如何解…

网络应用层HTTP协议

网络应用层HTTP协议 1. HTTP协议介绍 在互联网世界中&#xff0c;HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信&#xff0c;以交换或传输超文本(如 HTML 文档)。…

B+树的原理及实现

文章目录 B树的原理及实现一、引言二、B树的特性1、结构特点2、节点类型3、阶数 三、B树的Java实现1、节点实现2、B树操作2.1、搜索2.2、插入2.3、删除2.4、遍历 3、B树的Java实现示例 四、总结 B树的原理及实现 一、引言 B树是一种基于B树的树形数据结构&#xff0c;它在数据…

在 macOS 中,设置自动将文件夹排在最前

文章目录 1、第一步访达设置2、第二步排序方式 需要两步设置 1、第一步访达设置 按名称排序的窗口中 2、第二步排序方式 选择名称

【数据库】Unity 使用 Sqlite 数据库

1.找到需要三个 DLL Mono.Data.Sqlite.dllSystem.Data.dllsqlite3.dll 上面两个dll可在本地unity安装目录找到&#xff1a; C:\Program Files\Unity\Hub\Editor\2022.3.xxf1c1\Editor\Data\MonoBleedingEdge\lib\mono\unityjit-win32 下面dll可在sqlite官网下载到&#xff…

国内使用博查SearchAPI进行智能搜索,通过API获取搜索引擎的天气、日历、百科、手机、火车票等信息

在现代开发中&#xff0c;网络资源搜索是关键且常见的需求。博查SearchAPI作为国内领先的智能搜索解决方案&#xff0c;已服务超过2000家企业和16000名开发者&#xff0c;获得腾讯元器、字节扣子、阿里钉钉等官方推荐。该API提供近百亿网页内容及多样的生态合作内容&#xff0c…

免费网站源码下载指南:如何安全获取并降低开发成本

许多开发者或是需要建立网站的人&#xff0c;可以方便地获取免费网站源码。这样的下载能帮助他们降低开发费用&#xff0c;迅速构建起基本框架。但在此过程中&#xff0c;仍有许多需要注意的事项。 许多开发者或是需要建立网站的人&#xff0c;可以方便地获取免费网站源码。这…

colnames看似简单,却能优化数据处理流程

引言 在数据处理和分析中&#xff0c;变量名称是至关重要的&#xff0c;它们决定了数据的可读性和操作的简便性。在R语言中&#xff0c;colnames 函数以其简单的语法设计&#xff0c;提供了高效管理数据框列名的能力&#xff0c;尤其是在复杂的爬虫任务中显得尤为重要。本篇文…

计算机网络例题

IP地址分类&#xff1a; A类&#xff1a;网络号范围&#xff1a; 1~126 &#xff08;0000 0001 ~ 0111 1110&#xff09; B类&#xff1a;网络号范围&#xff1a;128.1 ~ 191.255 &#xff08;可用范围&#xff09; C类&#xff1a;网络号段范围&#xff1a;192.0.1 ~ 223.2…

腾讯云AI代码助手编程挑战赛 - 使用 JavaScript 构建一个简易日历

功能简介&#xff1a; 动态年份选择&#xff1a;用户可以通过下拉框选择从 2000 年到 2050 年的任意年份。全年日历生成&#xff1a;根据用户选择的年份&#xff0c;动态生成该年份的所有 12 个月份的日历。直观的 UI 设计&#xff1a;使用 CSS 美化日历外观&#xff0c;使日历…

hive迁移后修复分区慢,怎么办?

我有1个30TB的分区表&#xff0c;客户给的带宽只有600MB&#xff0c;按照150%的耗时来算&#xff0c;大概要迁移17小时。 使用hive自带的修复分区命令&#xff08;一般修复分区比迁移时间长一点&#xff09;&#xff0c;可能要花24小时。于是打算用前面黄大佬的牛B方案。 Hive增…

java-方法详解

目录​​​​​​​ 一、方法的定义 二、方法的调用 1.对于非静态方法&#xff1a; 2.对于静态方法&#xff1a; 3.类名.什么情况下可以省略 三、方法的参数传递 (1).实参和形参 形参 实参 (2).基本数据类型参数传递&#xff1a; (3).引用数据类型参数传递&#xff…