在 Golang 中构建 CRUD 应用程序(下)

简介: 在本教程中,我们将在 Golang 中构建一个 CRUD 应用程序。我们将使用 gorilla/mux 库作为 api 和 PostgreSQL DB 来存储数据。

中间件

中间件包是 API 和数据库之间的桥梁。这个包将处理所有的数据库操作,如插入、选择、更新和删除 (CRUD)。

创建一个新文件夹 middleware 并在其中创建一个新文件 handlers.go

粘贴以下代码。

package middleware
import (
    "database/sql"
    "encoding/json" // package to encode and decode the json into struct and vice versa
    "fmt"
    "go-postgres/models" // models package where User schema is defined
    "log"
    "net/http" // used to access the request and response object of the api
    "os"       // used to read the environment variable
    "strconv"  // package used to covert string into int type
    "github.com/gorilla/mux" // used to get the params from the route
    "github.com/joho/godotenv" // package used to read the .env file
    _ "github.com/lib/pq"      // postgres golang driver
)
// response format
type response struct {
    ID      int64  `json:"id,omitempty"`
    Message string `json:"message,omitempty"`
}
// create connection with postgres db
func createConnection() *sql.DB {
    // load .env file
    err := godotenv.Load(".env")
    if err != nil {
        log.Fatalf("Error loading .env file")
    }
    // Open the connection
    db, err := sql.Open("postgres", os.Getenv("POSTGRES_URL"))
    if err != nil {
        panic(err)
    }
    // check the connection
    err = db.Ping()
    if err != nil {
        panic(err)
    }
    fmt.Println("Successfully connected!")
    // return the connection
    return db
}
// CreateUser create a user in the postgres db
func CreateUser(w http.ResponseWriter, r *http.Request) {
    // set the header to content type x-www-form-urlencoded
    // Allow all origin to handle cors issue
    w.Header().Set("Context-Type", "application/x-www-form-urlencoded")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "POST")
    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    // create an empty user of type models.User
    var user models.User
    // decode the json request to user
    err := json.NewDecoder(r.Body).Decode(&user)
    if err != nil {
        log.Fatalf("Unable to decode the request body.  %v", err)
    }
    // call insert user function and pass the user
    insertID := insertUser(user)
    // format a response object
    res := response{
        ID:      insertID,
        Message: "User created successfully",
    }
    // send the response
    json.NewEncoder(w).Encode(res)
}
// GetUser will return a single user by its id
func GetUser(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Context-Type", "application/x-www-form-urlencoded")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    // get the userid from the request params, key is "id"
    params := mux.Vars(r)
    // convert the id type from string to int
    id, err := strconv.Atoi(params["id"])
    if err != nil {
        log.Fatalf("Unable to convert the string into int.  %v", err)
    }
    // call the getUser function with user id to retrieve a single user
    user, err := getUser(int64(id))
    if err != nil {
        log.Fatalf("Unable to get user. %v", err)
    }
    // send the response
    json.NewEncoder(w).Encode(user)
}
// GetAllUser will return all the users
func GetAllUser(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Context-Type", "application/x-www-form-urlencoded")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    // get all the users in the db
    users, err := getAllUsers()
    if err != nil {
        log.Fatalf("Unable to get all user. %v", err)
    }
    // send all the users as response
    json.NewEncoder(w).Encode(users)
}
// UpdateUser update user's detail in the postgres db
func UpdateUser(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "PUT")
    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    // get the userid from the request params, key is "id"
    params := mux.Vars(r)
    // convert the id type from string to int
    id, err := strconv.Atoi(params["id"])
    if err != nil {
        log.Fatalf("Unable to convert the string into int.  %v", err)
    }
    // create an empty user of type models.User
    var user models.User
    // decode the json request to user
    err = json.NewDecoder(r.Body).Decode(&user)
    if err != nil {
        log.Fatalf("Unable to decode the request body.  %v", err)
    }
    // call update user to update the user
    updatedRows := updateUser(int64(id), user)
    // format the message string
    msg := fmt.Sprintf("User updated successfully. Total rows/record affected %v", updatedRows)
    // format the response message
    res := response{
        ID:      int64(id),
        Message: msg,
    }
    // send the response
    json.NewEncoder(w).Encode(res)
}
// DeleteUser delete user's detail in the postgres db
func DeleteUser(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Context-Type", "application/x-www-form-urlencoded")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "DELETE")
    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    // get the userid from the request params, key is "id"
    params := mux.Vars(r)
    // convert the id in string to int
    id, err := strconv.Atoi(params["id"])
    if err != nil {
        log.Fatalf("Unable to convert the string into int.  %v", err)
    }
    // call the deleteUser, convert the int to int64
    deletedRows := deleteUser(int64(id))
    // format the message string
    msg := fmt.Sprintf("User updated successfully. Total rows/record affected %v", deletedRows)
    // format the reponse message
    res := response{
        ID:      int64(id),
        Message: msg,
    }
    // send the response
    json.NewEncoder(w).Encode(res)
}
//------------------------- handler functions ----------------
// insert one user in the DB
func insertUser(user models.User) int64 {
    // create the postgres db connection
    db := createConnection()
    // close the db connection
    defer db.Close()
    // create the insert sql query
    // returning userid will return the id of the inserted user
    sqlStatement := `INSERT INTO users (name, location, age) VALUES ($1, $2, $3) RETURNING userid`
    // the inserted id will store in this id
    var id int64
    // execute the sql statement
    // Scan function will save the insert id in the id
    err := db.QueryRow(sqlStatement, user.Name, user.Location, user.Age).Scan(&id)
    if err != nil {
        log.Fatalf("Unable to execute the query. %v", err)
    }
    fmt.Printf("Inserted a single record %v", id)
    // return the inserted id
    return id
}
// get one user from the DB by its userid
func getUser(id int64) (models.User, error) {
    // create the postgres db connection
    db := createConnection()
    // close the db connection
    defer db.Close()
    // create a user of models.User type
    var user models.User
    // create the select sql query
    sqlStatement := `SELECT * FROM users WHERE userid=$1`
    // execute the sql statement
    row := db.QueryRow(sqlStatement, id)
    // unmarshal the row object to user
    err := row.Scan(&user.ID, &user.Name, &user.Age, &user.Location)
    switch err {
    case sql.ErrNoRows:
        fmt.Println("No rows were returned!")
        return user, nil
    case nil:
        return user, nil
    default:
        log.Fatalf("Unable to scan the row. %v", err)
    }
    // return empty user on error
    return user, err
}
// get one user from the DB by its userid
func getAllUsers() ([]models.User, error) {
    // create the postgres db connection
    db := createConnection()
    // close the db connection
    defer db.Close()
    var users []models.User
    // create the select sql query
    sqlStatement := `SELECT * FROM users`
    // execute the sql statement
    rows, err := db.Query(sqlStatement)
    if err != nil {
        log.Fatalf("Unable to execute the query. %v", err)
    }
    // close the statement
    defer rows.Close()
    // iterate over the rows
    for rows.Next() {
        var user models.User
        // unmarshal the row object to user
        err = rows.Scan(&user.ID, &user.Name, &user.Age, &user.Location)
        if err != nil {
            log.Fatalf("Unable to scan the row. %v", err)
        }
        // append the user in the users slice
        users = append(users, user)
    }
    // return empty user on error
    return users, err
}
// update user in the DB
func updateUser(id int64, user models.User) int64 {
    // create the postgres db connection
    db := createConnection()
    // close the db connection
    defer db.Close()
    // create the update sql query
    sqlStatement := `UPDATE users SET name=$2, location=$3, age=$4 WHERE userid=$1`
    // execute the sql statement
    res, err := db.Exec(sqlStatement, id, user.Name, user.Location, user.Age)
    if err != nil {
        log.Fatalf("Unable to execute the query. %v", err)
    }
    // check how many rows affected
    rowsAffected, err := res.RowsAffected()
    if err != nil {
        log.Fatalf("Error while checking the affected rows. %v", err)
    }
    fmt.Printf("Total rows/record affected %v", rowsAffected)
    return rowsAffected
}
// delete user in the DB
func deleteUser(id int64) int64 {
    // create the postgres db connection
    db := createConnection()
    // close the db connection
    defer db.Close()
    // create the delete sql query
    sqlStatement := `DELETE FROM users WHERE userid=$1`
    // execute the sql statement
    res, err := db.Exec(sqlStatement, id)
    if err != nil {
        log.Fatalf("Unable to execute the query. %v", err)
    }
    // check how many rows affected
    rowsAffected, err := res.RowsAffected()
    if err != nil {
        log.Fatalf("Error while checking the affected rows. %v", err)
    }
    fmt.Printf("Total rows/record affected %v", rowsAffected)
    return rowsAffected
}

让我们分解功能:

  • createConnection:此函数将创建与 postgreSQL 数据库的连接并返回数据库连接。

检查函数中的代码:

// use godotenv to load the .env file
err := godotenv.Load(".env")
// Read the POSTGRES_URL from the .env and connect to the db.
db, err := sql.Open("postgres", os.Getenv("POSTGRES_URL"))

在 go-postgres 中创建一个新文件 .env:

POSTGRES_URL="Postgres connection string"
  • CreateUser:这是可以访问 api 的请求和响应对象的处理函数。它将在用户中提取请求正文。然后,它会调用 insertUser 作为参数传递用户。 insertUser 将返回插入 id
  • insertUser:此函数将在数据库中执行插入查询。首先建立 db 连接。
// create the postgres db connection
db := createConnection()
// close the db connection
defer db.Close()

创建 SQL 查询:

sqlStatement := `INSERT INTO users (name, location, age) VALUES ($1, $2, $3) RETURNING userid`

我们没有传递用户 ID,因为用户 ID 是 SERIAL 类型。它的范围是 1 到 2,147,483,647。

每次插入都会增加。

RETURNING userid 意味着一旦在数据库中成功插入,就返回用户 ID。

执行插入查询

var id int64
err := db.QueryRow(sqlStatement, user.Name, user.Location, user.Age).Scan(&id)

在 QueryRow 中接受 sql 查询和参数。在 sqlStatement 中,VALUES 作为变量 $1、$2、$3 传递。 user.Name 是第一个参数,因此它将替换 $1。同样,所有参数都将根据它们的位置进行替换。

使用扫描返回用户 ID 将解码为 id

相关文章
|
2月前
|
监控 网络协议 Go
应用监控 eBPF 版:实现 Golang 微服务的无侵入应用监控
应用监控 eBPF 版:实现 Golang 微服务的无侵入应用监控
109702 118
|
2月前
|
Go
第五章 Golang程序流程控制
第五章 Golang程序流程控制
40 0
|
2月前
|
程序员 Go
Golang深入浅出之-Select语句在Go并发编程中的应用
【4月更文挑战第23天】Go语言中的`select`语句是并发编程的关键,用于协调多个通道的读写。它会阻塞直到某个通道操作可行,执行对应的代码块。常见问题包括忘记初始化通道、死锁和忽视`default`分支。要解决这些问题,需确保通道初始化、避免死锁并添加`default`分支以处理无数据可用的情况。理解并妥善处理这些问题能帮助编写更高效、健壮的并发程序。结合使用`context.Context`和定时器等工具,可提升`select`的灵活性和可控性。
40 2
|
26天前
|
负载均衡 监控 Go
使用Golang框架构建分布式系统
本文探讨了使用Golang构建分布式系统的方法。Golang因其高效、简洁的语法和并发支持成为理想的开发语言。文中列举了几个常用的Golang框架,如Echo、Gin、gRPC和NATS等,并强调了服务拆分、通信机制、负载均衡等构建分布式系统的关键要素。通过选择合适的框架,遵循需求分析、技术选型、服务设计等步骤,开发者可以构建出高性能、高可用和可扩展的系统。此外,文中还提供了一个使用gRPC和etcd的简单代码案例来说明实现过程。
44 4
|
2月前
|
网络协议 Go 数据安全/隐私保护
golang开源的可嵌入应用程序高性能的MQTT服务
golang开源的可嵌入应用程序高性能的MQTT服务
330 2
|
3天前
|
编译器 Go C语言
通过例子学习在golang中调试程序
【7月更文挑战第4天】Go语言支持使用cgo进行汇编调试,官方文档在golang.org/doc/asm。注意,调试Go运行时可能遇到变量不可用或行号错误,需谨慎使用step命令。
44 1
通过例子学习在golang中调试程序
|
10天前
|
SQL NoSQL Go
技术经验分享:Golang标准库:errors包应用
技术经验分享:Golang标准库:errors包应用
|
2月前
|
安全 Go
Golang深入浅出之-Go语言中的并发安全队列:实现与应用
【5月更文挑战第3天】本文探讨了Go语言中的并发安全队列,它是构建高性能并发系统的基础。文章介绍了两种实现方法:1) 使用`sync.Mutex`保护的简单队列,通过加锁解锁确保数据一致性;2) 使用通道(Channel)实现无锁队列,天生并发安全。同时,文中列举了并发编程中常见的死锁、数据竞争和通道阻塞问题,并给出了避免这些问题的策略,如明确锁边界、使用带缓冲通道、优雅处理关闭以及利用Go标准库。
47 5
|
2月前
|
负载均衡 监控 Go
Golang深入浅出之-Go语言中的服务网格(Service Mesh)原理与应用
【5月更文挑战第5天】服务网格是处理服务间通信的基础设施层,常由数据平面(代理,如Envoy)和控制平面(管理配置)组成。本文讨论了服务发现、负载均衡和追踪等常见问题及其解决方案,并展示了使用Go语言实现Envoy sidecar配置的例子,强调Go语言在构建服务网格中的优势。服务网格能提升微服务的管理和可观测性,正确应对问题能构建更健壮的分布式系统。
43 1
|
2月前
|
JSON 监控 安全
Golang深入浅出之-Go语言中的反射(reflect):原理与实战应用
【5月更文挑战第1天】Go语言的反射允许运行时检查和修改结构,主要通过`reflect`包的`Type`和`Value`实现。然而,滥用反射可能导致代码复杂和性能下降。要安全使用,应注意避免过度使用,始终进行类型检查,并尊重封装。反射的应用包括动态接口实现、JSON序列化和元编程。理解反射原理并谨慎使用是关键,应尽量保持代码静态类型。
40 2