使用 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 、快速搭建开发环境

2、构建 API

2 .1、创建目录,初始化项目

go mod init todo-list-api

安装依赖包:

go get -u github.com/gorilla/mux
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

完工了。

posted @   硅基喵  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示