Go标准库net/http操作CRUD

 0
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(&notes)
	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(&note, "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)
}

你的反应是什么?

like

dislike

love

funny

angry

sad

wow