内容简介
本专题将带你从零开始实现一个简单的、RESTFUL 风格的 Todo 服务。包括:JSON 响应及请求、PostgreSQL 的使用、自定义错误的处理、RESTFul 的定义、配置文件、日志的记录等。配置文件
本章我们将实现配置文件的加载。我们将对 axum 的监听地址和 PostgreSQL 相关的信息进行配置,并保存到`.env`文件中。错误处理
本章我们将自定义错误、自定义一个`Result`以及让它们作为 handler 的返回值,进行 HTTP 响应。自定义响应
我们的 Todo 服务是对外提供 API 的服务,它的响应格式总是`JSON`类型。为此,我们可以定义响应类型,以简化 handler 的编写。数据库、模型、状态共享及TodoList
现在是时候开始进行数据库操作,以便实现功能了。本章将实现`TodoList`的功能。日志及重构
本章我们将对之前的代码进行重构并且使用日志记录可能发生的错误。实现TodoItem
经过一番重构,目前我们的 Todo 服务已经基本完善了,现在只差最后一个部分:TodoItem。本章我们就来实现它。总结
经过一番不懈努力,我们终于完成了一个提供 RESTFul API 的 Todo 服务。虽然功能简单,但它涉及到了 Axum 开发的多个方面。
总结
经过一番不懈努力,我们终于完成了一个提供 RESTFul API 的 Todo 服务。虽然功能简单,但它涉及到了 Axum 开发的多个方面。
运行效果
我们先来看看这个小项目的运行效果,一是检验我们的成果,二是对功能进行简单测试。
TodoList 部分
创建 TodoList
$ curl -X POST -H 'content-type:application/json;charset=utf-8' -d '{"title":"axum中文网"}' 127.0.0.1:9527/todo
{"code":0,"msg":"OK","data":{"id":3}}
curl 参数说明:
-
-X POST
:指定使用POST
方式进行请求 -
-H 'content-type:application/json;charset=utf-8'
:指定我们的请求是 JSON 格式的 -
-d '{"title":"axum中文网"}'
:指定我们要传递的数据
-X POST
:指定使用POST
方式进行请求
-H 'content-type:application/json;charset=utf-8'
:指定我们的请求是 JSON 格式的
-d '{"title":"axum中文网"}'
:指定我们要传递的数据
返回信息符合我们的预期:
{ "code": 0, "msg": "OK", "data": { "id": 3 } }
新创建的 TodoList 的 ID 为 3,API 为我们返回了这个 ID。
$ curl 127.0.0.1:9527/todo
{"code":0,"msg":"OK","data":[{"id":3,"title":"axum中文网"},{"id":2,"title":"清单2"},{"id":1,"title":"清单1"}]}
可以看到,除了刚刚通过调用 API 创建的 ID 为 3 的记录之外,我们通过导入 SQL 文件创建的 2 条记录也在,符合预期。
获取单个 TodoList
$ curl 127.0.0.1:9527/todo/3
{"code":0,"msg":"OK","data":{"id":3,"title":"axum中文网"}}
修改 TodoList
$ curl -X PUT -H 'content-type:application/json;charset=utf-8' -d '{"title":"axum中文网,axum.rs", "id":3}' 127.0.0.1:9527/todo/3
{"code":0,"msg":"OK","data":true}
修改后,我们再次获取这个 TodoList 看看:
$ curl 127.0.0.1:9527/todo/3
{"code":0,"msg":"OK","data":{"id":3,"title":"axum中文网,axum.rs"}}
可以看到,ID 为 3 的 TodoList 的title
字段的数据已经成功修改了。
$ curl -X DELETE 127.0.0.1:9527/todo/3
{"code":0,"msg":"OK","data":true
删除后,我们再尝试获取这个 TodoList:
$ curl 127.0.0.1:9527/todo/3
{"code":2,"msg":"不存在的记录","data":null}
可以看到,ID 为 3 的 TodoList 已经不存在了。
TodoItem 部分
获取指定 TodoList 下的所有 TodoItem
$ curl 127.0.0.1:9527/todo/1/items
{"code":0,"msg":"OK","data":[{"id":1,"title":"清单项目 1","checked":false,"list_id":1},{"id":2,"title":"清单项目 2","checked":false,"list_id":1}]}
获取某个 TodoItem 的详情
$ curl 127.0.0.1:9527/todo/1/items/1
{"code":0,"msg":"OK","data":{"id":1,"title":"清单项目 1","checked":false,"list_id":1}}
即,将其checked
修改为true
:
curl -X PUT 127.0.0.1:9527/todo/1/items/1
{"code":0,"msg":"OK","data":true}
再获取它的详情看看:
$ curl 127.0.0.1:9527/todo/1/items/1
{"code":0,"msg":"OK","data":{"id":1,"title":"清单项目 1","checked":true,"list_id":1}}
删除某个 TodoItem
$ curl -X DELETE 127.0.0.1:9527/todo/1/items/1
{"code":0,"msg":"OK","data":true}
再尝试获取它:
curl 127.0.0.1:9527/todo/1/items/1
{"code":2,"msg":"不存在的记录","data":null}
curl -X POST -d '{"title":"学习axum中文网课程", "list_id":1}' -H 'content-type:application/json;charset=utf-8' 127.0.0.1:9527/todo/1/items
{"code":0,"msg":"OK","data":{"id":4}}
总结
本项目使用了很多 axum 知识,如果你对某些知识点不清楚,可以在我们的《漫游 axum》专题里查找。你可能才开始使用 axum 开发,对本专题中很多代码不理解,没关系,你先照着代码自行练习,多敲几遍再配合我们的《漫游 axum》专题,相信会慢慢理解的。
我们的这个 Todo 服务大概涉及到以下知识点:
-
配置文件:通过
dotenv
和config
两个 crate,将我们的配置文本和结构体进行映射 -
自定义错误和自定义
Result
:我们自定义了自己的错误AppError
,无论是数据库操作还是 handler,最终都是将错误转换成了AppError
。为了简化操作,我们还定义了自己的Result
。注意,每个项目只能定义一次自己的Result
。所以,我们在handler
模块又定义了一个HandlerResult
。 -
自定义响应:我们的响应是一个 JSON,它通过
code
来标识是否成功,通过msg
来传递可能出现的错误信息,通过data
来返回期望的数据。为了统一,我们将其封装成了Response
。同时,AppError
也在实现IntoResponse
的时候使用了它。 -
数据库连接:我们使用了
deadpool-postgres
这个连接池,它包装了tokio-postgres
。为了方便将查询结果填充到结构体,我们还使用了tokio-pg-mapper
和tokio-pg-mapper-derive
。 -
日志:虽然我们将所有可能出现的错误都转换成了
AppError
,但并没有对这些错误进行记录。为了记录这些错误,我们使用了tracing
和tracing-subscriber
。 -
重构:我们对代码进行了重构,这是很重要的。既能提高开发效率,又能易于扩展。尤其注意
handler
模块的log_error
是如何封装闭包的以及db
里的get_stmt
等函数是如何既能接收Client
又能接收事务的。
自定义错误和自定义Result
:我们自定义了自己的错误AppError
,无论是数据库操作还是 handler,最终都是将错误转换成了AppError
。为了简化操作,我们还定义了自己的Result
。注意,每个项目只能定义一次自己的Result
。所以,我们在handler
模块又定义了一个HandlerResult
。
自定义响应:我们的响应是一个 JSON,它通过code
来标识是否成功,通过msg
来传递可能出现的错误信息,通过data
来返回期望的数据。为了统一,我们将其封装成了Response
。同时,AppError
也在实现IntoResponse
的时候使用了它。
数据库连接:我们使用了deadpool-postgres
这个连接池,它包装了tokio-postgres
。为了方便将查询结果填充到结构体,我们还使用了tokio-pg-mapper
和tokio-pg-mapper-derive
。
日志:虽然我们将所有可能出现的错误都转换成了AppError
,但并没有对这些错误进行记录。为了记录这些错误,我们使用了tracing
和tracing-subscriber
。
重构:我们对代码进行了重构,这是很重要的。既能提高开发效率,又能易于扩展。尤其注意 handler
模块的log_error
是如何封装闭包的以及db
里的get_stmt
等函数是如何既能接收Client
又能接收事务的。
请特别关注我们是如何对代码进行重构的。