使用 Go 构建一个最小的 API 应用
最近有项目要使用 Go 开发,作为一个. NET Core 选手,准备先撸一个包含 CRUD 的最小 MVP 项目练手。
要创建一个 TODO 应用,会创建下面这些接口:
API | Description | Request body | Response body |
---|---|---|---|
GET /todoitems |
Get all to-do items | None | Array of to-do items |
GET /todoitems/complete |
Get completed to-do items | None | Array of to-do items |
GET /todoitems/{id} |
Get an item by ID | None | To-do item |
POST /todoitems |
Add a new item | To-do item | To-do item |
PUT /todoitems/{id} |
Update an existing item | To-do item | None |
DELETE /todoitems/{id} |
Delete an item | None | None |
我觉得,做这样一个 API 应用,不管是 Go 还是其他语言,思路是一样的,无外乎:SDK 版本、开发工具、服务容器、HTTP 请求和响应处理、数据库对应的语言驱动、实体定义和映射、JSON 处理等等。因此,其他语言怎么做,换成 Go 之后,找对应的工具和实现方案就可以了。
1 、快速搭建开发环境
- 官方下载 SDK:Download and install - The Go Programming Language
- 安装 VS Code:Download Visual Studio Code - Mac, Linux, Windows
- 安装扩展:"Go"
- 安装 Go 工具包:
ctrl+shift+p
,输入go install
回车后,选择全部工具安装
2、构建 API
2 .1、创建目录,初始化项目
go mod init todo-list-api
安装依赖包:
- gorilla/mux 是一个强大的 HTTP 路由器和 URL 匹配器,用于构建 Go Web 服务器: gorilla/mux: Package gorilla/mux is a powerful HTTP router and URL matcher for building Go web servers with 🦍 (github.com)
go get -u github.com/gorilla/mux
- Postgres 数据库的 Go 驱动包:lib/pq: Pure Go Postgres driver for database/sql (github.com)
go get github.com/lib/pq
2.2、入口函数
新增 main.go
文件,内容如下:
func main() {
r := mux.NewRouter()
r.HandleFunc("/api/todoitems", GetToDoItems).Methods("GET")
r.HandleFunc("/api/todoitems/complete", GetToDoItemInCompleted).Methods("GET")
r.HandleFunc("/api/todoitems/{id}", GetToDoItemById).Methods("GET")
r.HandleFunc("/api/todoitems", CreateToDoItem).Methods("POST")
r.HandleFunc("/api/todoitems/{id}", DeleteToDoItem).Methods("DELETE")
r.HandleFunc("/api/todoitems/{id}", UpdateToDoItem).Methods("PUT")
srv := &http.Server{
Handler: r,
Addr: ":3000",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
HandleFunc
函数第一个参数是路由路径,第二个参数是处理函数,然后链式调用 Methods 指定可以处理的 HTTP 请求类型。
2.3、实体和数据库
新增 db.go
文件,内容如下:
var DB *sql.DB
func ConnectToDatabase() {
connStr := "user=postgres dbname=postgres password=Mysoft7789 sslmode=disable"
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatal(err)
}
DB = db
}
这个函数用来打开并获取数据库连接,放在 init 方法中调用
func init() {
ConnectToDatabase()
}
定义结构类型 ToDo
,内容如下:
type Todo struct {
Id int `json:"id"`
Name string `json:"name"`
IsComplete bool `json:"is_complete"`
}
创建 postgres 数据表
create table public.t_todo_item
(
id integer generated by default as identity,
name varchar(20),
is_complete boolean
);
2 .4、实现 API
新增 todo-service.go
实现所有的 API
func GetToDoItems(w http.ResponseWriter, r *http.Request) {
rows, err := DB.Query("select id,name,is_complete from t_todo_item")
if err != nil {
panic(err.Error())
}
var todos []Todo
for rows.Next() {
var todo Todo
err = rows.Scan(&todo.Id, &todo.Name, &todo.IsComplete)
if err != nil {
log.Printf("Error happened in scan row. Err: %s", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
todos = append(todos, todo)
}
jsonResp, err := json.Marshal(todos)
if err != nil {
log.Fatalf("Error happened in JSON marshal. Err: %s", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResp)
}
func GetToDoItemInCompleted(w http.ResponseWriter, r *http.Request) {
rows, err := DB.Query("select id,name,is_complete from t_todo_item where is_complete = true")
if err != nil {
panic(err.Error())
}
var todos []Todo
for rows.Next() {
var todo Todo
err = rows.Scan(&todo.Id, &todo.Name, &todo.IsComplete)
if err != nil {
log.Printf("Error happened in scan row. Err: %s", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
todos = append(todos, todo)
}
jsonResp, err := json.Marshal(todos)
if err != nil {
log.Fatalf("Error happened in JSON marshal. Err: %s", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResp)
}
func CreateToDoItem(w http.ResponseWriter, r *http.Request) {
// convert todo struct from body
var todo Todo
err := json.NewDecoder(r.Body).Decode(&todo)
if err != nil {
log.Printf("Error happened in JSON unmarshal. Err: %s", err)
return
}
// insert into db
err = DB.QueryRow("insert into t_todo_item (name, is_complete) values ($1, $2) RETURNING id", todo.Name, false).Scan(&todo.Id)
if err != nil {
log.Printf("Error happened in insert into db. Err: %s", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
// return created todo
jsonResp, _ := json.Marshal(todo)
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResp)
}
func GetToDoItemById(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
row := DB.QueryRow("select id,name, is_complete from t_todo_item where id = $1", id)
var todo Todo
row.Scan(&todo.Id, &todo.Name, &todo.IsComplete)
jsonResp, err := json.Marshal(todo)
if err != nil {
log.Fatalf("Error happened in JSON marshal. Err: %s", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResp)
}
func DeleteToDoItem(w http.ResponseWriter, r *http.Request) {
// check id from url
vars := mux.Vars(r)
// convert id to int
id, err := strconv.Atoi(vars["id"])
if err != nil {
log.Printf("Error happened in convert id to int. Err: %s", err)
w.WriteHeader(http.StatusBadRequest)
return
}
// if id less then 0, return 400
if condition := id < 0; condition {
w.WriteHeader(http.StatusBadRequest)
return
}
// delete from db
_, err = DB.Exec("delete from t_todo_item where id = $1", id)
if err != nil {
log.Printf("Error happened in delete from db. Err: %s", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
func UpdateToDoItem(w http.ResponseWriter, r *http.Request) {
// check id from url
vars := mux.Vars(r)
// convert id to int
id, err := strconv.Atoi(vars["id"])
if err != nil {
log.Printf("Error happened in convert id to int. Err: %s", err)
w.WriteHeader(http.StatusBadRequest)
return
}
// if id less then 0, return 400
if condition := id < 0; condition {
w.WriteHeader(http.StatusBadRequest)
return
}
// convert todo struct from body
var todo Todo
err = json.NewDecoder(r.Body).Decode(&todo)
if err != nil {
log.Printf("Error happened in JSON unmarshal. Err: %s", err)
}
todo.Id = id
// update db
_, err = DB.Exec("update t_todo_item set name = $1, is_complete = $2 where id = $3", todo.Name, todo.IsComplete, id)
if err != nil {
log.Printf("Error happened in update db. Err: %s", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
2.5、测试验证
新增一个 todo.http
文件,内容如下:
# GET request to retrieve all todo items
GET http://localhost:3000/api/todoitems HTTP/1.1
###
# GET request to retrieve a specific todo item by id
GET http://localhost:3000/api/todoitems/20 HTTP/1.1
###
# GET request to retrieve all completed todo items
GET http://localhost:3000/api/todoitems/complete HTTP/1.1
###
# POST request to create a new todo item
POST http://localhost:3000/api/todoitems HTTP/1.1
Content-Type: application/json
{
"name": "Buy grocerie1"
}
###
# PUT request to update an existing todo item
PUT http://localhost:3000/api/todoitems/20 HTTP/1.1
Content-Type: application/json
{
"name": "Buy groceries",
"is_complete": true
}
###
# DELETE request to delete a todo item
DELETE http://localhost:3000/api/todoitems/2 HTTP/1.1
完工了。
本文来自博客园,作者:硅基喵,转载请注明原文链接:https://www.cnblogs.com/denglei1024/p/18473057
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!