Go标准库net/http操作CRUD
Go 1.22发布net/http包提供了在Golang标准库中构建Web API高级路由所需的一切。net/http包对于中间件、子路由、路径参数、HTTP 方法提供高级功能。
使用 net/http 包构建构建一个CRUD API的路线图:
使用GORM设置SQLite数据库
创建路由处理程序处理HTTP请求建立路由
目录
使用SQLC和PostgreSQL的CRUD RESTful API
导入https://github.com/go-standard-crud appNote App.postman_collection.json根目录中的文件。
包含HTTP请求,使用前端应用程序运行 API
首先,安装了Node.js和PNPM。
安装依赖项后,运行命令pnpm dev启动 Vite 开发服务器。
浏览器中打开应用程序http://localhost:3000/的React应用程序
设置 Golang 项目
生成一个新的Go项目。运行命令创建一个新目录初始化Golang项目。
go get github.com/go-playground/validator/v10
go get gorm.io/driver/sqlite
go get -u gorm.io/gorm
go get github.com/google/uuid
go get github.com/rs/cors
validator 允许根据添加到结构字段的标签来验证结构字段。
sqlite Go中的GORM ORM库提供 SQLite驱动。
gorm 处理Go中数据库操作的ORM库。
uuid 在Go中生成UUID。
cors Go中处理CORS相关标头和请求的包。
设置GORM进行数据库操作
安装依赖项后,开始处理与数据库相关的任务,包括创建数据库模式、处理验证逻辑连接到 SQLite 服务器。
创建数据库架构
创建名为Note的GORM模型,使用该模型通过GORM在数据库中生成其对应的表和列。model.go在根目录中创建一个:model.go
model.go
package main
import (
"time"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
"gorm.io/gorm"
)
type Note struct {
ID string `gorm:"type:char(36);primary_key" json:"id,omitempty"`
Title string `gorm:"type:varchar(255);uniqueIndex:idx_notes_title,LENGTH(255);not null" json:"title,omitempty"`
Content string `gorm:"not null" json:"content,omitempty"`
Category string `gorm:"varchar(100)" json:"category,omitempty"`
Published bool `gorm:"default:false;not null" json:"published"`
CreatedAt time.Time `gorm:"not null;default:'1970-01-01 00:00:01'" json:"createdAt,omitempty"`
UpdatedAt time.Time `gorm:"not null;default:'1970-01-01 00:00:01';ON UPDATE CURRENT_TIMESTAMP" json:"updatedAt,omitempty"`
}
func (note *Note) BeforeCreate(tx *gorm.DB) (err error) {
note.ID = uuid.New().String()
return nil
}
var validate = validator.New()
type ErrorResponse struct {
Field string `json:"field"`
Tag string `json:"tag"`
Value string `json:"value,omitempty"`
}
func ValidateStruct[T any](payload T) []*ErrorResponse {
var errors []*ErrorResponse
err := validate.Struct(payload)
if err != nil {
for _, err := range err.(validator.ValidationErrors) {
var element ErrorResponse
element.Field = err.StructNamespace()
element.Tag = err.Tag()
element.Value = err.Param()
errors = append(errors, &element)
}
}
return errors
}
type CreateNoteSchema struct {
Title string `json:"title" validate:"required"`
Content string `json:"content" validate:"required"`
Category string `json:"category,omitempty"`
Published bool `json:"published,omitempty"`
}
type UpdateNoteSchema struct {
Title string `json:"title,omitempty"`
Content string `json:"content,omitempty"`
Category string `json:"category,omitempty"`
Published *bool `json:"published,omitempty"`
}
连接到数据库服务器
经定义了GORM模型创建一个程序函数来SQLite数据库建立连接池自动将GORM模型迁移到连接池。
initializer.go
package main
import (
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var DB *gorm.DB
func ConnectDB() error {
var err error
DB, err = gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
if err != nil {
return err
}
DB.Logger = logger.Default.LogMode(logger.Info)
log.Println("Running Migrations")
err = DB.AutoMigrate(&Note{})
if err != nil {
return err
}
log.Println("???? Connected Successfully to the Database")
return nil
}
CRUD操作
数据库相关的代码创建路由处理程序(控制器),使用GORM库提供的CRUD函数对数据库执行操作。先在handlers.go根目录中导入和包定义:
package main
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"time"
"gorm.io/gorm"
)
执行 CREATE 操作
func CreateNoteHandler(w http.ResponseWriter, r *http.Request) {
var payload CreateNoteSchema
// Decode JSON request body into the payload struct
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "fail",
"message": err.Error(),
})
return
}
// Validate payload struct
errors := ValidateStruct(&payload)
if errors != nil {
w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(errors)
return
}
now := time.Now()
newNote := Note{
Title: payload.Title,
Content: payload.Content,
Category: payload.Category,
Published: payload.Published,
CreatedAt: now,
UpdatedAt: now,
}
// Save new note to the database
result := DB.Create(&newNote)
if result.Error != nil {
if strings.Contains(result.Error.Error(), "UNIQUE constraint failed") {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusConflict)
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "fail",
"message": "Title already exists, please use another title",
})
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadGateway)
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "error",
"message": result.Error.Error(),
})
return
}
// Return success response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "success",
"data": map[string]interface{}{
"note": newNote,
},
})
}
执行读取操作
需要两个读取操作:
用于检索分页的项目列表
用于按特定 ID 检索项目
func FindNotes(w http.ResponseWriter, r *http.Request) {
page := r.URL.Query().Get("page")
limit := r.URL.Query().Get("limit")
if page == "" {
page = "1"
}
if limit == "" {
limit = "10"
}
intPage, err := strconv.Atoi(page)
if err != nil {
http.Error(w, "Invalid page parameter", http.StatusBadRequest)
return
}
intLimit, err := strconv.Atoi(limit)
if err != nil {
http.Error(w, "Invalid limit parameter", http.StatusBadRequest)
return
}
offset := (intPage - 1) * intLimit
var notes []Note
results := DB.Limit(intLimit).Offset(offset).Find(¬es)
if results.Error != nil {
http.Error(w, results.Error.Error(), http.StatusBadGateway)
return
}
// Return success response
response := map[string]interface{}{
"status": "success",
"results": len(notes),
"notes": notes,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
handlers.go
func FindNoteById(w http.ResponseWriter, r *http.Request) {
noteID := r.PathValue("noteId")
var note Note
result := DB.First(¬e, "id = ?", noteID)
if err := result.Error; err != nil {
if err == gorm.ErrRecordNotFound {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
response := map[string]interface{}{
"status": "fail",
"message": "No note with that ID exists",
}
json.NewEncoder(w).Encode(response)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadGateway)
response := map[string]interface{}{
"status": "fail",
"message": err.Error(),
}
json.NewEncoder(w).Encode(response)
return
}
response := map[string]interface{}{
"status": "success",
"data": map[string]interface{}{
"note": note,
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
HTTP处理程序创建路由
HTTP路由处理程序创建路由、连接数据库、设置CORS启动HTTP服务器。
main.go代码:
package main
import (
"encoding/json"
"log"
"net/http"
"time"
"github.com/rs/cors"
)
func init() {
// Initialize database
err := ConnectDB()
if err != nil {
log.Fatalf("Failed to connect to the database: %v", err)
}
}
func main() {
// Create Router
router := http.NewServeMux()
router.HandleFunc("GET /api/healthchecker", HealthCheckHandler)
router.HandleFunc("PATCH /api/notes/{noteId}", UpdateNote)
router.HandleFunc("GET /api/notes/{noteId}", FindNoteById)
router.HandleFunc("DELETE /api/notes/{noteId}", DeleteNote)
router.HandleFunc("POST /api/notes/", CreateNoteHandler)
router.HandleFunc("GET /api/notes/", FindNotes)
// Custom CORS configuration
corsConfig := cors.New(cors.Options{
AllowedHeaders: []string{"Origin", "Authorization", "Accept", "Content-Type"},
AllowedOrigins: []string{"http://localhost:3000"},
AllowedMethods: []string{"GET", "POST", "PATCH", "DELETE"},
AllowCredentials: true,
})
// Wrap the router with the logRequests middleware
loggedRouter := logRequests(router)
// Create a new CORS handler
corsHandler := corsConfig.Handler(loggedRouter)
server := http.Server{
Addr: ":8000",
Handler: corsHandler,
}
log.Println("Starting server on port :8000")
if err := server.ListenAndServe(); err != nil {
log.Fatalf("Server error: %v", err)
}
}
type wrappedWriter struct {
http.ResponseWriter
statusCode int
}
func (w *wrappedWriter) WriteHeader(statusCode int) {
w.ResponseWriter.WriteHeader(statusCode)
w.statusCode = statusCode
}
func logRequests(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
wrapped := &wrappedWriter{
ResponseWriter: w,
statusCode: http.StatusOK,
}
next.ServeHTTP(wrapped, r)
elapsed := time.Since(start)
log.Printf("Received request: %d %s %s %s", wrapped.statusCode, r.Method, r.URL.Path, elapsed)
})
}
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
response := map[string]string{
"status": "success",
"message": "Welcome to Go standard library",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
你的反应是什么?