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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git撤回提交、删除远端某版本、合并指定版本的更改

撤回提交 vscode的举例 一、只提交了还未推送的情况下 1.撤回最后一次提交&#xff0c;把最后一次提交的更改放到暂存区 git reset --soft HEAD~12.撤回最后一次提交&#xff0c;把最后一次提交的更改放到工作区 git reset --mixed HEAD~13.撤回最后一次提交&#xff0c;不…

[Linux]redis5.0.x升级至7.x完整操作流程

1. 从官网下载最新版redis&#xff1a; 官网地址&#xff1a;https://redis.io/download 注&#xff1a;下载需要的登录&#xff0c;如果选择使用github账号登录&#xff0c;那么需要提前在github账号中取消勾选“Keep my email addresses private”&#xff08;隐藏我的邮箱…

xss-labs关卡记录15-20关

十五关 随便传一个参数&#xff0c;然后右击查看源码发现&#xff0c;这里有一个陌生的东西&#xff0c;就是ng-include。这里就是&#xff1a; ng-include指令就是文件包涵的意思&#xff0c;用来包涵外部的html文件&#xff0c;如果包涵的内容是地址&#xff0c;需要加引号。…

数据库回滚:大祸临头时

原文地址 什么是数据库回滚&#xff1f; 数据库技术中&#xff0c;回滚是通过撤销对数据库所做的一项或多项更改&#xff0c;将数据库返回到先前状态的操作。它是维护数据完整性和从错误中恢复的重要机制。 什么时候需要数据库回滚&#xff1f; 数据库回滚在以下几个场景中很…

年会抽奖Html

在这里插入图片描述 <!-- <video id"backgroundMusic" src"file:///D:/background.mp3" loop autoplay></video> --> <divstyle"width: 290px; height: 580px; margin-left: 20px; margin-top: 20px; background: url(D:/nianhu…

基于FPGA的出租车里程时间计费器

基于FPGA的出租车里程时间计费器 功能描述一、系统框图二、verilog代码里程增加模块时间增加模块计算价格模块上板视频演示 总结 功能描述 &#xff08;1&#xff09;&#xff1b;里程计费功能&#xff1a;3公里以内起步价8元&#xff0c;超过3公里后每公里2元&#xff0c;其中…

nginx-链路追踪(trace)实现

一. 需求场景&#xff1a; 在日常运维工作中&#xff0c;会经常遇到在有多重调用链的场景下&#xff0c;如请求遇到非致命error时&#xff0c;在各环节的定位会非常麻烦&#xff0c;举个例子&#xff1a;比如说&#xff0c;在一个有多重调用链的服务环境下&#xff0c;一个请求…

c#使用SevenZipSharp实现压缩文件和目录

封装了一个类&#xff0c;方便使用SevenZipSharp&#xff0c;支持加入进度显示事件。 双重加密压缩工具范例&#xff1a; using SevenZip; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.…

MySQL和Hive中的行转列、列转行

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 MySQL1.行转列2.列转行 Hive1.行转列2.列转行(1)侧窗(2)union MySQL 1.行转列 把多行转成列。直接group&#xff0c;sum(if()) 2.列转行 Hive 1.行转列 select name,sum(if(kmshuxu…

快速上手:采用Let‘sEncrypt免费SSL证书配置网站Https (示例环境:Centos7.9+Nginx+Let‘sEncrypt)

1 关于Let’s Encrypt与Cerbot DNS验证 Let’s Encrypt 是一个提供 免费证书 的 认证机构。 Cerbot 是 Let’s Encrypt 提供的一个工具&#xff0c;用于自动化生成、验证和续订证书。 DNS验证是 Cerbot 支持的验证方式之一。相比 HTTP 验证或 TLS-ALPN 验证&#xff0c;DNS …

【Unity3D】Text文本文字掉落效果

相关技术&#xff1a;Text、TextMesh、Rigidbody&#xff08;刚体&#xff09;、BoxCollider&#xff08;碰撞体&#xff09;、TextGenerator、文本网格、文字网格 原理&#xff1a;使用UGUI Text获取其文字的每个字符网格坐标&#xff0c;转世界坐标生成对应的3D文本(TextMesh…

flutter 专题二十四 Flutter性能优化在携程酒店的实践

Flutter性能优化在携程酒店的实践 一 、前言 携程酒店业务使用Flutter技术开发的时间快接近两年&#xff0c;这期间有列表页、详情页、相册页等页面使用了Flutter技术栈进行了跨平台整合&#xff0c;大大提高了研发效率。在开发过程中&#xff0c;也遇到了一些性能相关问题和…

设计模式 行为型 命令模式(Command Pattern)与 常见技术框架应用 解析

命令模式&#xff08;Command Pattern&#xff09;是一种行为型设计模式&#xff0c;它旨在将请求发送者和接收者解耦&#xff0c;通过将一个请求封装为一个对象&#xff0c;从而允许参数化客户端对象以进行不同的请求、排队请求或记录请求&#xff0c;并支持可撤销操作。 在软…

NodeLocal DNS 全攻略:从原理到应用实践

文章目录 一、NodeLocal DNS是什么&#xff1f;二、为什么使用NodeLocal DNS&#xff1f;三、工作原理架构图四、安装NodeLocal DNS五、在应用中使用NodeLocal DNSCache六、验证 一、NodeLocal DNS是什么&#xff1f; NodeLocal DNSCache 通过在集群节点上运行一个 DaemonSet …

jenkins入门12-- 权限管理

Jenkins的权限管理 由于jenkins默认的权限管理体系不支持用户组或角色的配置&#xff0c;因此需要安装第三发插件来支持角色的配置&#xff0c;我们使用Role-based Authorization Strategy 插件 只有项目读权限 只有某个项目执行权限

STM32-笔记34-4G遥控灯

4G接线 一、项目需求 服务器通过4G模块远程遥控开关灯。 二、项目实现 复制项目文件夹38-wifi控制风扇项目 重命名为39-4G遥控点灯 打开项目文件 加载文件 main.c #include "sys.h" #include "delay.h" #include "led.h" #include "ua…

STM32——系统滴答定时器(SysTick寄存器详解)

文章目录 1.SysTick简介2.工作原理3.SysTick寄存器4.代码延时逻辑5.附上整体代码6.一些重要解释 1.SysTick简介 Cortex-M处理器内集成了一个小型的名为SysTick(系统节拍)的定时器,它属于NVIC的一部分,且可以产生 SysTick异常(异常类型#15)。SysTick为简单的向下计数的24位计数…

数据库模型全解析:从文档存储到搜索引擎

目录 前言1. 文档存储&#xff08;Document Store&#xff09;1.1 概念与特点1.2 典型应用1.3 代表性数据库 2. 图数据库&#xff08;Graph DBMS&#xff09;2.1 概念与特点2.2 典型应用2.3 代表性数据库 3. 原生 XML 数据库&#xff08;Native XML DBMS&#xff09;3.1 概念与…

使用 Conda创建新的环境遇到的问题

下载速度很慢 1、更新 conda update -n base -c defaults conda2、清理缓存 conda clean --all解决方法 方法 1&#xff1a;关闭严格的渠道优先级 检查是否开启了严格渠道优先级&#xff1a; conda config --show channel_priority 如果返回 strict&#xff0c;说明启用了严…

软件23种设计模式完整版[附Java版示例代码]

一、什么是设计模式 设计模式是在软件设计中反复出现的问题的通用解决方案。它们是经过多次验证和应用的指导原则,旨在帮助软件开发人员解决特定类型的问题,提高代码的可维护性、可扩展性和重用性。 设计模式是一种抽象化的思维方式,可以帮助开发人员更好地组织和设计他们…