使用Express.js和SQLite3构建简单TODO应用的后端API
- 引言
- 环境准备
- 代码解析
- 1. 导入必要的模块
- 2. 创建Express应用实例
- 3. 设置数据库连接
- 4. 初始化数据库表
- 5. 配置中间件
- 6. 定义数据接口
- 7. 定义路由
- 7.1 获取所有TODO项
- 7.2 创建TODO项
- 7.3 更新TODO项
- 7.4 删除TODO项
- 8. 启动服务器
- 优化建议
- 总结
引言
在现代Web开发中,TODO列表应用是一个经典的示例,用于展示如何使用前端和后端技术构建一个简单的任务管理工具。本文将详细介绍如何使用Express.js框架和SQLite3数据库来构建一个TODO列表应用,并解释代码的各个部分,帮助读者理解其工作原理。
前端UI请参考,使用React和Material-UI构建TODO应用的前端UI
环境准备
在开始之前,请确保你已经安装了以下工具和库:
- Node.js:确保你已经安装了Node.js,可以从Node.js官网下载并安装。
- npm:Node.js的包管理工具,随Node.js一起安装。
- Express.js:一个轻量级的Node.js Web应用框架,可以通过npm安装。
- SQLite3:一个文件数据库,适用于小型应用,同样可以通过npm安装。
- TypeScript:可选,但推荐使用,以提升代码的可维护性和类型安全性。
安装所需的依赖:
npm install express cors sqlite3
代码解析
让我们逐步分析代码,理解每个部分的功能。
1. 导入必要的模块
import express from 'express';
import cors from 'cors';
import sqlite3 from 'sqlite3';
import path from 'path';
- Express:用于创建Web服务器。
- CORS:处理跨域请求,允许前端应用从不同的域名访问后端API。
- SQLite3:用于与SQLite数据库进行交互。
- Path:处理文件路径,确保在不同操作系统下路径正确。
2. 创建Express应用实例
const app = express();
这行代码创建了一个Express应用实例,后续的所有中间件和路由都将注册到这个实例上。
3. 设置数据库连接
const db = new sqlite3.Database(path.join(__dirname, '../database/db.sqlite'));
这里创建了一个SQLite3数据库连接,数据库文件位于../database/db.sqlite
。path.join
用于确保路径在不同操作系统下都能正确解析。
4. 初始化数据库表
db.serialize(() => {db.run(`CREATE TABLE IF NOT EXISTS todos(id INTEGER PRIMARY KEY AUTOINCREMENT,title TEXT NOT NULL,description TEXT,completed BOOLEAN DEFAULT 0,created_at DATETIME DEFAULT CURRENT_TIMESTAMP)`);
});
这段代码在数据库中创建了一个名为todos
的表,如果该表不存在的话。表结构如下:
- id:整数类型,主键,自增。
- title:文本类型,非空。
- description:文本类型,可选。
- completed:布尔类型,默认值为
0
(即false
)。 - created_at:日期时间类型,默认值为当前时间戳。
5. 配置中间件
app.use(cors());
app.use(express.json());
- CORS中间件:允许来自不同域名的请求,防止跨域问题。
- JSON中间件:解析请求体中的JSON数据,使得
req.body
可以访问到客户端发送的数据。
6. 定义数据接口
interface Todo {id?: number;title: string;description?: string;completed?: boolean;created_at?: string;
}
这是一个TypeScript接口,定义了TODO项的结构。id
、description
、completed
和created_at
是可选的,而title
是必填的。
7. 定义路由
7.1 获取所有TODO项
app.get('/api/todos', (req: express.Request, res: express.Response) => {const search = (req.query.q as string) || '';db.all<Todo>(`SELECT *FROM todosWHERE title ILIKE ? OR description ILIKE ?ORDER BY created_at DESC`,[`%${search}%`, `%${search}%`],(err, rows) => {if (err) return res.status(500).json({ error: err.message });res.json(rows);});
});
- 功能:获取所有TODO项,支持通过
q
参数进行模糊搜索。 - 查询参数:
q
用于模糊搜索标题或描述。 - 数据库操作:使用
ILIKE
进行不区分大小写的匹配,按创建时间降序排列。 - 错误处理:如果数据库操作失败,返回500状态码和错误信息。
7.2 创建TODO项
app.post('/api/todos', (req: express.Request, res: express.Response) => {const { title, description } = req.body as Todo;if (!title) {return res.status(400).json({ error: 'Title is required' });}db.run('INSERT INTO todos (title, description) VALUES (?, ?)',[title, description],function (err) {if (err) return res.status(500).json({ error: err.message });db.get<Todo>('SELECT * FROM todos WHERE id = ?',[this.lastID],(err, row) => {if (err) return res.status(500).json({ error: err.message });res.status(201).json(row);});});
});
- 功能:创建一个新的TODO项。
- 请求体:必须包含
title
,description
可选。 - 数据库操作:插入新记录,然后查询新插入的记录并返回。
- 错误处理:检查
title
是否为空,返回400错误;数据库操作失败返回500错误。
7.3 更新TODO项
app.put('/api/todos/:id', (req: express.Request, res: express.Response) => {const { id } = req.params;const { title, description, completed } = req.body as Todo;db.run(`UPDATE todosSET title = ?,description = ?,completed = ?WHERE id = ?`,[title, description, completed ? 1 : 0, id],function (err) {if (err) return res.status(500).json({ error: err.message });if (this.changes === 0) {return res.status(404).json({ error: 'Todo not found' });}db.get<Todo>('SELECT * FROM todos WHERE id = ?',[id],(err, row) => {if (err) return res.status(500).json({ error: err.message });res.json(row);});});
});
- 功能:更新指定ID的TODO项。
- 请求体:包含
title
、description
和completed
。 - 数据库操作:更新记录,
completed
字段转换为1
或0
。 - 错误处理:如果没有记录被更新,返回404错误;数据库操作失败返回500错误。
7.4 删除TODO项
app.delete('/api/todos/:id', (req: express.Request, res: express.Response) => {const { id } = req.params;db.run('DELETE FROM todos WHERE id = ?',[id],function (err) {if (err) return res.status(500).json({ error: err.message });if (this.changes === 0) {return res.status(404).json({ error: 'Todo not found' });}res.status(204).send();});
});
- 功能:删除指定ID的TODO项。
- 数据库操作:删除记录。
- 错误处理:如果没有记录被删除,返回404错误;数据库操作失败返回500错误。
8. 启动服务器
const PORT = 3002;
app.listen(PORT, () => {console.log(`Server running on port ${PORT}`);
});
服务器监听在3002端口,启动后在控制台输出提示信息。
优化建议
尽管这段代码已经可以正常工作,但为了提升应用的性能和可维护性,可以考虑以下优化:
- 使用连接池: SQLite3支持连接池,可以提高数据库操作的效率,特别是在高并发情况下。
- 添加输入验证:使用中间件如
express-validator
对请求体进行验证,确保数据的合法性和完整性。 - 添加分页功能:在获取TODO列表时,添加分页功能,限制每页返回的记录数,提高性能。
- 使用ORM工具:如
Sequelize
或Knex.js
,简化数据库操作,提高代码的可读性和可维护性。 - 添加日志记录:使用
winston
或morgan
记录请求和错误信息,便于调试和监控。 - 添加速率限制:防止恶意攻击,限制同一IP地址的请求频率。
总结
通过本文,我们详细解析了一个使用Express.js和SQLite3构建的TODO列表应用。从数据库的初始化、路由的定义到错误处理,每个部分都进行了详细的解释。希望这篇文章能够帮助读者理解如何使用这些技术构建一个简单的Web应用,并为进一步的学习和开发打下基础。