使用Express.js和SQLite3构建简单TODO应用的后端API
-
-
-
- 引言
- 环境准备
- 代码解析
-
- [1. 导入必要的模块](#1. 导入必要的模块)
- [2. 创建Express应用实例](#2. 创建Express应用实例)
- [3. 设置数据库连接](#3. 设置数据库连接)
- [4. 初始化数据库表](#4. 初始化数据库表)
- [5. 配置中间件](#5. 配置中间件)
- [6. 定义数据接口](#6. 定义数据接口)
- [7. 定义路由](#7. 定义路由)
-
- [7.1 获取所有TODO项](#7.1 获取所有TODO项)
- [7.2 创建TODO项](#7.2 创建TODO项)
- [7.3 更新TODO项](#7.3 更新TODO项)
- [7.4 删除TODO项](#7.4 删除TODO项)
- [8. 启动服务器](#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:可选,但推荐使用,以提升代码的可维护性和类型安全性。
安装所需的依赖:
bash
npm install express cors sqlite3
代码解析
让我们逐步分析代码,理解每个部分的功能。
1. 导入必要的模块
typescript
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应用实例
typescript
const app = express();
这行代码创建了一个Express应用实例,后续的所有中间件和路由都将注册到这个实例上。
3. 设置数据库连接
typescript
const db = new sqlite3.Database(path.join(__dirname, '../database/db.sqlite'));
这里创建了一个SQLite3数据库连接,数据库文件位于../database/db.sqlite
。path.join
用于确保路径在不同操作系统下都能正确解析。
4. 初始化数据库表
typescript
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. 配置中间件
typescript
app.use(cors());
app.use(express.json());
- CORS中间件:允许来自不同域名的请求,防止跨域问题。
- JSON中间件 :解析请求体中的JSON数据,使得
req.body
可以访问到客户端发送的数据。
6. 定义数据接口
typescript
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项
typescript
app.get('/api/todos', (req: express.Request, res: express.Response) => {
const search = (req.query.q as string) || '';
db.all<Todo>(
`SELECT *
FROM todos
WHERE 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项
typescript
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项
typescript
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 todos
SET 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项
typescript
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. 启动服务器
typescript
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应用,并为进一步的学习和开发打下基础。