介绍
PostgreSQL是当今最流行的SQL数据库之一。根据官方文档,它是“一个功能强大、开源的对象关系数据库系统,经过30多年的积极开发,在可靠性、功能健壮性和性能方面赢得了良好的声誉。”
在本文中,我们将研究如何在Go应用程序中使用Postgres。
先决条件
在我们开始使用这个应用程序之前,我们需要设置以下几件事:
- Go-由于这是我们选择的编程语言,我们需要在本地环境中安装它
- PostgreSQL-我们将使用PostgreSQL作为我们的数据库。因此,出于开发目的,您需要在本地环境中安装它。然而,在生产中,您可能会考虑一个更健壮和安全的解决方案,如云服务。AWS Aurora就是一个例子。您可以从官方网站下载PostgreSQL
- pgAdmin 4-这是一个用户界面,允许我们直观地管理Postgres数据库。您可以在此处下载pgAdmin
我们将构建的内容:一个简单的待办事项应用程序
我们将构建一个全栈web应用程序,允许我们在Postgres数据库上执行CRUD操作。基本上,我们将构建一个待办应用程序。以下是完成的应用程序的外观:
这个应用程序允许我们从数据库中获取、添加、编辑和删除待办事项。不用多说,让我们开始吧。
创建名为server的文件。进入项目文件夹并添加以下代码:
package main import ( "fmt" "log" "os" "github.com/gofiber/fiber/v2" ) func main() { app := fiber.New() port := os.Getenv("PORT") if port == "" { port = "3000" } log.Fatalln(app.Listen(fmt.Sprintf(":%v", port))) }
我们首先导入操作系统模块、日志模块,当然还有我们选择的web框架,在本例中是Go Fiber。如果您对Go Fiber没有太多经验,这里有一个Go Fibre文档的链接供您查看。
我们正在做的是用纤维制造一个新的纤维物体。新建并将其分配给app变量。接下来,我们检查环境变量中名为PORT的变量,如果不存在,我们将端口指定为3000。
然后我们调用app。侦听以启动正在侦听端口的HTTP服务器。接下来,我们调用日志。Fatalln()在出现任何错误时将输出记录到控制台。在运行此代码之前,让我们添加一些路由:
func main() { app := fiber.New() app.Get("/", indexHandler) // Add this app.Post("/", postHandler) // Add this app.Put("/update", putHandler) // Add this app.Delete("/delete", deleteHandler) // Add this port := os.Getenv("PORT") if port == "" { port = "3000" } log.Fatalln(app.Listen(fmt.Sprintf(":%v", port))) }
如您所见,我为我们的应用程序添加了四个处理GET、POST、PUT和DELETE操作的方法,以及每当有人访问这些路由时调用的四个处理程序方法。现在,让我们定义这些方法,以便Go停止抛出错误:
func indexHandler(c *fiber.Ctx) error { return c.SendString("Hello") } func postHandler(c *fiber.Ctx) error { return c.SendString("Hello") } func putHandler(c *fiber.Ctx) error { return c.SendString("Hello") } func deleteHandler(c *fiber.Ctx) error { return c.SendString("Hello") }
目前,我们只是在所有路线上返回“你好”。让我们运行我们的应用程序。在命令行上,运行命令"go mod init",然后运行"go mod tidy"。这将创造一个机会。mod文件并获取应用程序所需的所有依赖项。
为了在开发时进行热重新加载,我们需要一个名为Air的Go包。
使用“go-get-github.com/cosmtrek/air”导入。现在运行“go run github.com/cosmtrek/air”启动应用程序。这将启动我们的web服务器并监视项目目录中的所有文件,从而允许我们在文件更改时进行热重新加载。
现在访问http://localhost/查看应用程序。
让我们创建一个到数据库的连接。在主方法中,在创建Fiber应用程序实例之前,添加以下代码:
connStr := "postgresql://<username>:<password>@<database_ip>/todos?sslmode=disable " // Connect to database db, err := sql.Open("postgres", connStr) if err != nil { log.Fatal(err) }
确保将用户名、密码和database_ip替换为数据库的用户名、密码以及ip地址。
首先,我们需要导入用于连接数据库的SQL驱动程序。CockroachDB是一个SQL数据库,因此我们可以使用任何Go-Postgres/SQL数据库驱动程序连接到它。在我们的例子中,我们将使用pq驱动程序。将导入更新为此:
import ( "database/sql" // add this "fmt" "log" "os" _ "github.com/lib/pq" // add this "github.com/gofiber/fiber/v2" )
pq驱动程序依赖于数据库/sql包,因此我们也导入了它。我们不会直接使用pq驱动程序,因此我们在其导入前加上下划线。
我们将使用database/sql包执行所有数据库操作,如连接和执行查询。现在停止应用程序并运行“go get github.com/lib/pq”安装pq驱动程序。
接下来,我们将添加代码以创建数据库连接,并更新路由以将数据库连接传递给我们的处理程序,以便我们可以使用它执行数据库查询:
connStr := "postgresql://<username>:<password>@<database_ip>/todos?sslmode=disable" // Connect to database db, err := sql.Open("postgres", connStr) if err != nil { log.Fatal(err) } app := fiber.New() app.Get("/", func(c *fiber.Ctx) error { return indexHandler(c, db) }) app.Post("/", func(c *fiber.Ctx) error { return postHandler(c, db) }) app.Put("/update", func(c *fiber.Ctx) error { return putHandler(c, db) }) app.Delete("/delete", func(c *fiber.Ctx) error { return deleteHandler(c, db) })
如您所见,我们现在正在传递一个函数来代替我们的处理程序,该函数接受fiber上下文对象,并将其与数据库连接一起传递给处理程序。fiber上下文对象包含关于传入请求的所有内容,如头、查询字符串参数、帖子正文等。有关详细信息,请参阅fiber文档。
现在,让我们更新处理程序以接受指向数据库连接的指针:
func indexHandler(c *fiber.Ctx, db *sql.DB) error { return c.SendString("Hello") } func postHandler(c *fiber.Ctx, db *sql.DB) error { return c.SendString("Hello") } func putHandler(c *fiber.Ctx, db *sql.DB) error { return c.SendString("Hello") } func deleteHandler(c *fiber.Ctx, db *sql.DB) error { return c.SendString("Hello") } Now start the app again and you see it runs without errors. Here’s the full code up to here for reference. package main import ( "database/sql" // add this "fmt" "log" "os" _ "github.com/lib/pq" // add this "github.com/gofiber/fiber/v2" ) func indexHandler(c *fiber.Ctx, db *sql.DB) error { return c.SendString("Hello") } func postHandler(c *fiber.Ctx, db *sql.DB) error { return c.SendString("Hello") } func putHandler(c *fiber.Ctx, db *sql.DB) error { return c.SendString("Hello") } func deleteHandler(c *fiber.Ctx, db *sql.DB) error { return c.SendString("Hello") } func main() { connStr := "postgresql://<username>:<password>@<database_ip>/todos?sslmode=disable" // Connect to database db, err := sql.Open("postgres", connStr) if err != nil { log.Fatal(err) } app := fiber.New() app.Get("/", func(c *fiber.Ctx) error { return indexHandler(c, db) }) app.Post("/", func(c *fiber.Ctx) error { return postHandler(c, db) }) app.Put("/update", func(c *fiber.Ctx) error { return putHandler(c, db) }) app.Delete("/delete", func(c *fiber.Ctx) error { return deleteHandler(c, db) }) port := os.Getenv("PORT") if port == "" { port = "3000" } log.Fatalln(app.Listen(fmt.Sprintf(":%v", port))) }
充实我们的路由处理器
在我们开始充实处理程序之前,让我们先建立数据库。导航到pgAdmin 4控制台并创建一个名为todos的数据库。
单击“保存”创建数据库。现在,扩展todos数据库,并在公共模式下,创建一个名为todos的新表,其中有一个列名为item。
您已经成功创建了我们要连接的数据库。关闭pgAdmin应用程序,让我们开始充实我们的处理程序方法。
将索引处理程序修改为:
func indexHandler(c *fiber.Ctx, db *sql.DB) error { var res string var todos []string rows, err := db.Query("SELECT * FROM todos") defer rows.Close() if err != nil { log.Fatalln(err) c.JSON("An error occured") } for rows.Next() { rows.Scan(&res) todos = append(todos, res) } return c.Render("index", fiber.Map{ "Todos": todos, }) }
好吧,这太多了!首先,我们使用db对象对带有db的数据库执行SQL查询。Query()函数。这将向我们返回与查询匹配的所有行以及可能发生的任何错误。我们称之为延迟行。Close()关闭行,并在函数完成时防止进一步枚举。我们检查是否有任何错误,然后遍历所有行,调用行。Next(),并使用行。Scan()方法将行的当前值分配给res变量,我们将其定义为字符串。然后,我们将res的值附加到todos数组。
注释行。Scan()要求您传入与数据库中存储的数据相对应的数据类型变量。例如,如果您有多个列,比如名称和年龄,您将传入一个包含字段名称和年龄的结构。有关详细信息,请参阅SQL文档。
然后,我们返回到索引视图,并将todos数组传入其中。关于视图,让我们配置我们的Fiber应用程序以提供HTML视图。因此,修改您的主要方法:
engine := html.New("./views", ".html") app := fiber.New(fiber.Config{ Views: engine, })
我们将Fiber应用程序配置为使用HTML模板引擎,并传入./views作为视图所在的路径。停止应用程序并使用go-get-github安装HTML引擎。com/gofiber/template/html,并确保也导入它。
然后,在项目根目录中创建一个名为视图的文件夹。在视图中,创建一个名为index.html的文件并添加以下代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="/style.css"/> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"/> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,600;0,700;0,800;1,300;1,400;1,600;1,700;1,800&display=swap"/> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"/> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.standalone.min.css"/> <title>Document</title> </head> <body> <div class="container m-5 p-2 rounded mx-auto bg-light shadow"> <!-- App title section --> <div class="row m-1 p-4"> <div class="col"> <div class="p-1 h1 text-primary text-center mx-auto display-inline-block"> <i class="fa fa-check bg-primary text-white rounded p-2"></i> <u>Todo List</u> </div> </div> </div> <!-- Create todo section --> <div class="row m-1 p-3"> <div class="col col-11 mx-auto"> <form action="/" method="POST" class="row bg-white rounded shadow-sm p-2 add-todo-wrapper align-items-center justify-content-center"> <div class="col"> <input name="Item" class="form-control form-control-lg border-0 add-todo-input bg-transparent rounded" type="text" placeholder="Add new .."> </div> <div class="col-auto px-0 mx-0 mr-2"> <button type="submit" class="btn btn-primary">Add</button> </div> </form> </div> </div> <div class="p-2 m-2 mx-4 border-black-25 border-bottom"></div> <!-- Todo list section --> <div class="row mx-1 px-5 pb-3 w-80"> <div class="col mx-auto"> <!-- Todo Item--> {{range .Todos}} <div class="row px-3 align-items-center todo-item editing rounded"> <div class="col px-1 m-1 d-flex align-items-center"> <input type="text" class="form-control form-control-lg border-0 edit-todo-input bg-transparent rounded px-3 d-none" readonly value="{{.}}" title="{{.}}" /> <input id="{{.}}" type="text" class="form-control form-control-lg border-0 edit-todo-input rounded px-3" value="{{.}}" /> </div> <div class="col-auto m-1 p-0 px-3 d-none"> </div> <div class="col-auto m-1 p-0 todo-actions"> <div class="row d-flex align-items-center justify-content-end"> <h5 class="m-0 p-0 px-2"> <i onclick="updateDb('{{.}}')" class="fa fa-pencil text-warning btn m-0 p-0" data-toggle="tooltip" data-placement="bottom" title="Edit todo"></i> </h5> <h5 class="m-0 p-0 px-2"> <i onclick="removeFromDb('{{.}}')" class="fa fa-trash-o text-danger btn m-0 p-0" data-toggle="tooltip" data-placement="bottom" title="Delete todo"></i> </h5> </div> </div> </div> {{end}} </div> </div> </div> </form> <script src="index.js"></script> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootlint/1.1.0/bootlint.min.js"></script> </body> </html>
这将遍历我们传递的todos数组,并显示每个项目。如果您检查该文件,您将看到我们也在链接样式表。创建一个名为public的文件夹,并在其中创建一个称为style的文件。css并添加以下代码:
body { font-family: "Open Sans", sans-serif; line-height: 1.6; } .add-todo-input, .edit-todo-input { outline: none; } .add-todo-input:focus, .edit-todo-input:focus { border: none !important; box-shadow: none !important; } .view-opt-label, .date-label { font-size: 0.8rem; } .edit-todo-input { font-size: 1.7rem !important; } .todo-actions { visibility: hidden !important; } .todo-item:hover .todo-actions { visibility: visible !important; } .todo-item.editing .todo-actions .edit-icon { display: none !important; }
现在,让我们配置Go来服务这个文件。在启动web服务器之前,将其添加到主方法中:
app.Static("/", "./public") // add this before starting the app log.Fatalln(app.Listen(fmt.Sprintf(":%v", port)))
再次启动应用程序,您应该会看到以下内容。
对于我们的其他处理程序,请这样修改它们:
type todo struct { Item string } func postHandler(c *fiber.Ctx, db *sql.DB) error { newTodo := todo{} if err := c.BodyParser(&newTodo); err != nil { log.Printf("An error occured: %v", err) return c.SendString(err.Error()) } fmt.Printf("%v", newTodo) if newTodo.Item != "" { _, err := db.Exec("INSERT into todos VALUES ($1)", newTodo.Item) if err != nil { log.Fatalf("An error occured while executing query: %v", err) } } return c.Redirect("/") } func putHandler(c *fiber.Ctx, db *sql.DB) error { olditem := c.Query("olditem") newitem := c.Query("newitem") db.Exec("UPDATE todos SET item=$1 WHERE item=$2", newitem, olditem) return c.Redirect("/") } func deleteHandler(c *fiber.Ctx, db *sql.DB) error { todoToDelete := c.Query("item") db.Exec("DELETE from todos WHERE item=$1", todoToDelete) return c.SendString("deleted") }
首先,我们创建一个结构来保存待办事项。然后,在我们的postHandler中,我们从请求主体中获取要插入数据库的待办事项的名称。接下来,我们使用db.Exec()方法执行一个SQL查询,我们将新的待办事项添加到数据库中。然后我们重定向回主页。
N、 我们使用db.Query()方法。不执行时执行db.Exec()再次,请参阅SQL文档以了解更多信息。
对于我们的put处理程序,我们从请求查询字符串参数中获取新旧项名称。然后我们执行一个查询,用数据库中的新名称替换旧名称。最后,我们重定向回主页。
对于我们的delete处理程序,我们从请求查询字符串参数中获取要删除的名称,并执行一个查询从数据库中删除该名称,然后我们返回一个表示“deleted”的字符串。我们正在返回此字符串,以便知道函数已成功完成。
如果你检查索引。html文件,您会注意到,每当您分别单击编辑按钮和删除按钮时,我们都在调用updateDb和deleteFromDb函数。
这些函数已在索引中定义。js文件。这是索引。js文件看起来像:
function removeFromDb(item){ fetch(`/delete?item=${item}`, {method: "Delete"}).then(res =>{ if (res.status == 200){ window.location.pathname = "/" } }) } function updateDb(item) { let input = document.getElementById(item) let newitem = input.value fetch(`/update?olditem=${item}&newitem=${newitem}`, {method: "PUT"}).then(res =>{ if (res.status == 200){ alert("Database updated") window.location.pathname = "/" } }) } Now add the above code in a file called index.js in the public folder. Ok here’s the full server.go file code for a reference package main import ( "database/sql" // add this "fmt" "log" "os" _ "github.com/lib/pq" // add this "github.com/gofiber/fiber/v2" "github.com/gofiber/template/html" ) func indexHandler(c *fiber.Ctx, db *sql.DB) error { var res string var todos []string rows, err := db.Query("SELECT * FROM todos") defer rows.Close() if err != nil { log.Fatalln(err) c.JSON("An error occured") } for rows.Next() { rows.Scan(&res) todos = append(todos, res) } return c.Render("index", fiber.Map{ "Todos": todos, }) } type todo struct { Item string } func postHandler(c *fiber.Ctx, db *sql.DB) error { newTodo := todo{} if err := c.BodyParser(&newTodo); err != nil { log.Printf("An error occured: %v", err) return c.SendString(err.Error()) } fmt.Printf("%v", newTodo) if newTodo.Item != "" { _, err := db.Exec("INSERT into todos VALUES ($1)", newTodo.Item) if err != nil { log.Fatalf("An error occured while executing query: %v", err) } } return c.Redirect("/") } func putHandler(c *fiber.Ctx, db *sql.DB) error { olditem := c.Query("olditem") newitem := c.Query("newitem") db.Exec("UPDATE todos SET item=$1 WHERE item=$2", newitem, olditem) return c.Redirect("/") } func deleteHandler(c *fiber.Ctx, db *sql.DB) error { todoToDelete := c.Query("item") db.Exec("DELETE from todos WHERE item=$1", todoToDelete) return c.SendString("deleted") } func main() { connStr := "postgresql://postgres:gopher@localhost/todos?sslmode=disable" // Connect to database db, err := sql.Open("postgres", connStr) if err != nil { log.Fatal(err) } engine := html.New("./views", ".html") app := fiber.New(fiber.Config{ Views: engine, }) app.Get("/", func(c *fiber.Ctx) error { return indexHandler(c, db) }) app.Post("/", func(c *fiber.Ctx) error { return postHandler(c, db) }) app.Put("/update", func(c *fiber.Ctx) error { return putHandler(c, db) }) app.Delete("/delete", func(c *fiber.Ctx) error { return deleteHandler(c, db) }) port := os.Getenv("PORT") if port == "" { port = "3000" } app.Static("/", "./public") log.Fatalln(app.Listen(fmt.Sprintf(":%v", port))) }
如果您正确遵循了以上教程,那么您的应用程序应该是这样的:
结论
本教程终于结束了。我们已经了解了如何使用Go连接到PostgreSQL数据库,并成功地用它构建了一个待办应用程序。有很多方法可以改进这一点,我迫不及待地想看看你接下来要做什么。谢谢你的阅读。
- 登录 发表评论