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
相关文章:

Express 加 sqlite3 写一个简单博客
例图: 搭建 命令: 前提已装好node.js 开始创建项目结构 npm init -y package.json:{"name": "ex01","version": "1.0.0","main": "index.js","scripts": {"test": &q…...
正则表达式进阶学习(一):环视、捕获分组与后向引用
一、环视(零宽断言) 理论部分 环视(零宽断言)是一种用于匹配位置而非字符的正则表达式技术。它的核心特点是:不消耗字符,只检查某个位置前后是否符合特定的条件。可以理解为,环视是在匹配前“…...

《Vue3 七》插槽 Slot
插槽可以让组件的使用者来决定组件中的某一块区域到底存放什么元素和内容。 使用插槽: 插槽的使用过程其实就是抽取共性、预留不同。将共同的元素、内容依然留在组件内进行封装;将不同的元素使用 slot 作为占位,让外部决定到底显示什么样的…...

【C++数据结构——线性表】顺序表的基本运算(头歌实践教学平台习题)【合集】
目录😋 任务描述 相关知识 一、线性表的基本概念 二、初始化线性表 三、销毁线性表 四、判定是否为空表 五、求线性表的长度 六、输出线性表 七、求线性表中某个数据元素值 八、按元素值查找 九、插入数据元素 十、删除数据元素 测试说明 通关代码 测…...

Linux C/C++编程-获得套接字地址、主机名称和主机信息
【图书推荐】《Linux C与C一线开发实践(第2版)》_linux c与c一线开发实践pdf-CSDN博客《Linux C与C一线开发实践(第2版)(Linux技术丛书)》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com…...
USB kbtab linux 驱动代码
#include <linux/kernel.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/usb/input.h> #include <asm/unaligned.h> /* Pressure-threshold modules param code from */MODULE_AUTHOR(“xxx”); MODULE_DESCRIPTION(“…...

力扣 跳跃游戏
每次更新目标位置时,实际上是在做一个局部的最优选择,选择跳跃能够到达当前目标位置的最远位置。因为每次更新目标位置时,都是基于当前能跳跃到的最远位置,因此最终的结果是全局最优的。 题目 从前往后遍历,更新可以到…...
使用npm 插件[mmdc]将.mmd时序图转换为图片
使用npm 插件[mmdc]将.mmd时序图转换为图片 1. 安装 mmdc2. 转换为图片 可以使用 mmdc (Mermaid CLI)这个工具来将 .mmd 时序图(Mermaid语法描述的时序图)转换为图片,以下是使用步骤: 1. 安装 mmdc 确保…...

ffmpeg 常用命令
更详细请参考ffmpeg手册,下载ffmpegrelease版后在doc中就有,主页面。video filter 参考ffmpeg-filters.html -version -formats -demuxers -protocols -muxers -filters -devices —pix_fmts -codecs -sample_fmts -decoders -layouts -encoders -colors…...
从入门到实战:C 语言 strlen 函数通关指南
文章目录 一、strlen函数简介1. 函数构成2. 参数说明3. 使用示例 二、模拟实现strlen函数(从新手角度逐步升级改进)1. 基础版本(利用循环计数)2. 改进版本(利用指针相减)3. 递归版本(利用递归思…...

npm install --global windows-build-tools --save 失败
注意以下点 为啥下载windows-build-tools,是因为node-sass4.14.1 一直下载不成功,提示python2 没有安装,最终要安装这个,但是安装这个又失败,主要有以下几个要注意的 1、node 版本 14.21.3 不能太高 2、管理员运行 …...

十种基础排序算法(C语言实现,带源码)(有具体排序例子,适合学习理解)
学习了十种常见的排序方法,此文章针对所学的排序方法进行整理(通过C语言完成排序)。 参考内容: https://blog.csdn.net/mwj327720862/article/details/80498455 https://www.runoob.com/w3cnote/ten-sorting-algorithm.html 1. 冒…...

基于fMRI数据计算脑脊液(CSF)与全脑BOLD信号的时间耦合分析
一、前言 笔者之前的文章《基于Dpabi和spm12的脑脊液(csf)分割和提取笔记》,介绍了如何从普通的fMRI数据中提取CSF信号。首先是基础的预处理,包括时间层校正、头动校正,再加上0.01-0.1Hz的带通滤波。接着用SPM12分割出CSF区域,设置一个比较严格的0.9阈值,确保提取的真是…...

实现websocket心跳检测,断线重连机制
WebSocket基础 WebSocket概念 WebSocket是一种革命性的 全双工通信协议 ,构建在TCP之上,旨在简化客户端与服务器之间的数据交换过程。通过单次握手建立持久连接,WebSocket实现了真正的双向实时通信,显著提高了交互效率。这一特性…...

ComfyUI节点安装笔记
AI高速发展,版本更新相当快(11月25日才安装的版本v.0.3.4,27日版本就已经更新到v.0.3.5了),在遇到问题,找到问题原因所在的过程中,ComfyUI版本、python版本、节点对环境版本的依赖,本…...
深度学习,训练集准确率高,但验证集准确率一直不上升,很低的问题
在训练过程中,训练集的准确率稳步上升,但是验证集的准确率一直在40%左右徘徊,从网上搜索可能的原因有: 1、学习率太小,陷入局部最优。 2、数据量太小(4000多条数据,应该还可以吧) …...

【C语言程序设计——选择结构程序设计】求输入的日期是该年的第几天(头歌实践教学平台习题)【合集】
目录😋 任务描述 相关知识 1、switch 结构基本语法 2、示例代码及解释 3、使用注意事项 4、判断闰年的条件 编程要求 测试说明 通关代码 测试结果 任务描述 本关任务:编写程序实现:从键盘上输入一个年月日(以空格或回车…...
Lumos学习王佩丰Excel二十四讲系列完结
“Lumos学习王佩丰Excel二十四讲系列”是一套完整的Excel教程,涵盖了从基础到高级的各种知识和技能。是我亲自一个个码出来的教程哇!!! 一、课程概览 该教程共分为24讲,每一讲都围绕Excel的一个核心主题进行深入讲解…...

前后端规约
文章目录 引言I 【强制】前后端交互的 API请求内容响应体响应码II 【推荐】MVC响应体III【参考】IV 其他引言 服务器内部重定向必须使用 forward;外部重定向地址必须使用 URL 统一代理模块生成,否则会因线上采用 HTTPS 协议而导致浏览器提示“不安全”,并且还会带来 URL 维护…...
【数据可视化】数据可视化看板需求梳理模板(含示例)
一、模板 设计一个数据可视化看板需要从多个方面梳理需求,以确保看板能够有效地传达信息并满足用户的需求。以下是一些关键方面: 1.目标和受众 ● 明确目标:确定看板的主要目的,例如监控业务指标、分析市场趋势、展示项目进度等…...

OpenCV 图像色彩空间转换与抠图
一、知识点: 1、色彩空间转换函数 (1)、void cvtColor( InputArray src, OutputArray dst, int code, int dstCn 0, AlgorithmHint hint cv::ALGO_HINT_DEFAULT ); (2)、将图像从一种颜色空间转换为另一种。 (3)、参数说明: src: 输入图像,即要进行颜…...
【Go语言基础【5】】Go module概述:项目与依赖管理
文章目录 一、Go Module 概述二、Go Module 核心特性1. 项目结构2. 依赖查找机制 三、如何启用 Go Module四、创建 Go Module 项目五、Go Module 关键命令 一、Go Module 概述 Go Module 是 Go 1.11 版本(2018 年 8 月)引入的依赖管理系统,用…...
数论——同余问题全家桶3 __int128和同余方程组
数论——同余问题全家桶3 __int128和同余方程组 快速读写和__int128快速读写__int128 中国剩余定理和线性同余方程组中国剩余定理(CRT)中国剩余定理OJ示例模板题曹冲养猪 - 洛谷模板题猜数字 - 洛谷 扩展中国剩余定理扩展中国剩余定理OJ示例模板题扩展中国剩余定理(…...

[蓝桥杯]兰顿蚂蚁
兰顿蚂蚁 题目描述 兰顿蚂蚁,是于 1986 年,由克里斯兰顿提出来的,属于细胞自动机的一种。 平面上的正方形格子被填上黑色或白色。在其中一格正方形内有一只"蚂蚁"。 蚂蚁的头部朝向为:上下左右其中一方。 蚂蚁的移…...
【学习笔记】单例类模板
【学习笔记】单例类模板 一、单例类模板 以下为一个通用的单例模式框架,这种设计允许其他类通过继承Singleton模板类来轻松实现单例模式,而无需为每个类重复编写单例实现代码。 // 命名空间(Namespace) 和 模板(Tem…...
PART 6 树莓派小车+QT (TCP控制)
1. 树莓派作为服务器的程序 (1)服务器tcp_server_socket程序 可以实现小车前进、后退、左转、右转、加减速(可能不行) carMoveControl.py import RPi.GPIO as GPIO import time import tty,sys,select,termios import socket…...

Cesium使用glb模型、图片标记来实现实时轨迹
目录 1、使用glb模型进行实时轨迹 2、使用图片进行实时轨迹 基于上一篇加载基础地图的代码上继续开发 vue中加载Cesium地图(天地图、高德地图)-CSDN博客文章浏览阅读164次。vue中加载Cesium三维地球https://blog.csdn.net/ssy001128/article/details…...

计算机视觉顶刊《International Journal of Computer Vision》2025年5月前沿热点可视化分析
追踪计算机视觉领域的前沿热点是把握技术发展方向、推动创新落地的关键,分析这些热点,不仅能洞察技术趋势,更能为科研选题和工程实践提供重要参考。本文对计算机视觉顶刊《International Journal of Computer Vision》2025年5月前沿热点进行了…...
【Linux基础知识系列】第十四篇-系统监控与性能优化
一、简介 随着信息技术的飞速发展,Linux系统在服务器领域占据着重要地位。无论是web服务器、数据库服务器还是文件服务器,都需要高效的运行以满足业务需求。系统监控与性能优化是确保Linux系统稳定、高效运行的关键任务。通过实时监测系统资源的使用情况…...
vue-21 (使用 Vuex 模块和异步操作构建复杂应用)
实践练习:使用 Vuex 模块和异步操作构建复杂应用 Vuex 模块提供了一种结构化的方式来组织你的应用程序状态,特别是当应用程序变得复杂时。命名空间模块通过防止命名冲突和提高代码可维护性来增强这种组织。异步操作对于处理从 API 获取数据等操作至关重要,这些操作在现代 W…...