使用 Gin、FerretDB 和 oapi-codegen 建立部落格 API

在本教學中,我們將逐步介紹使用 Go 為簡單部落格應用程式建立 RESTful API 的過程。我們將使用以下技術:
- Gin:Go 的 Web 框架
- FerretDB:相容 MongoDB 的資料庫
- oapi-codegen:依據 OpenAPI 3.0 規格產生 Go 伺服器樣板的工具
目錄
- 設定項目
- 定義 API 規格
- 產生伺服器程式碼
- 實作資料庫層
- 實作 API 處理程序
- 運行應用程式
- 測試 API
- 結論
設定項目
首先,讓我們設定 Go 專案並安裝必要的依賴項:
mkdir blog-api cd blog-api go mod init github.com/yourusername/blog-api go get github.com/gin-gonic/gin go get github.com/deepmap/oapi-codegen/cmd/oapi-codegen go get github.com/FerretDB/FerretDB
定義API規範
在專案根目錄中建立一個名為 api.yaml 的文件,並為我們的部落格 API 定義 OpenAPI 3.0 規格:
openapi: 3.0.0
info:
title: Blog API
version: 1.0.0
paths:
/posts:
get:
summary: List all posts
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Post'
post:
summary: Create a new post
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NewPost'
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/Post'
/posts/{id}:
get:
summary: Get a post by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/Post'
put:
summary: Update a post
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NewPost'
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/Post'
delete:
summary: Delete a post
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'204':
description: Successful response
components:
schemas:
Post:
type: object
properties:
id:
type: string
title:
type: string
content:
type: string
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
NewPost:
type: object
required:
- title
- content
properties:
title:
type: string
content:
type: string
產生伺服器程式碼
現在,讓我們使用 oapi-codegen 根據我們的 API 規格產生伺服器程式碼:
oapi-codegen -package api api.yaml > api/api.go
此指令將建立一個名為 api 的新目錄,並產生包含伺服器介面和模型的 api.go 檔案。
實施資料庫層
建立一個名為 db/db.go 的新文件,以使用 FerretDB 實作資料庫層:
package db
import (
"context"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Post struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Title string `bson:"title"`
Content string `bson:"content"`
CreatedAt time.Time `bson:"createdAt"`
UpdatedAt time.Time `bson:"updatedAt"`
}
type DB struct {
client *mongo.Client
posts *mongo.Collection
}
func NewDB(uri string) (*DB, error) {
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(uri))
if err != nil {
return nil, err
}
db := client.Database("blog")
posts := db.Collection("posts")
return &DB{
client: client,
posts: posts,
}, nil
}
func (db *DB) Close() error {
return db.client.Disconnect(context.Background())
}
func (db *DB) CreatePost(title, content string) (*Post, error) {
post := &Post{
Title: title,
Content: content,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
result, err := db.posts.InsertOne(context.Background(), post)
if err != nil {
return nil, err
}
post.ID = result.InsertedID.(primitive.ObjectID)
return post, nil
}
func (db *DB) GetPost(id string) (*Post, error) {
objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
var post Post
err = db.posts.FindOne(context.Background(), bson.M{"_id": objectID}).Decode(&post)
if err != nil {
return nil, err
}
return &post, nil
}
func (db *DB) UpdatePost(id, title, content string) (*Post, error) {
objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
update := bson.M{
"$set": bson.M{
"title": title,
"content": content,
"updatedAt": time.Now(),
},
}
var post Post
err = db.posts.FindOneAndUpdate(
context.Background(),
bson.M{"_id": objectID},
update,
options.FindOneAndUpdate().SetReturnDocument(options.After),
).Decode(&post)
if err != nil {
return nil, err
}
return &post, nil
}
func (db *DB) DeletePost(id string) error {
objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
_, err = db.posts.DeleteOne(context.Background(), bson.M{"_id": objectID})
return err
}
func (db *DB) ListPosts() ([]*Post, error) {
cursor, err := db.posts.Find(context.Background(), bson.M{})
if err != nil {
return nil, err
}
defer cursor.Close(context.Background())
var posts []*Post
for cursor.Next(context.Background()) {
var post Post
if err := cursor.Decode(&post); err != nil {
return nil, err
}
posts = append(posts, &post)
}
return posts, nil
}
實作 API 處理程序
建立一個名為 handlers/handlers.go 的新檔案來實作 API 處理程序:
package handlers
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/yourusername/blog-api/api"
"github.com/yourusername/blog-api/db"
)
type BlogAPI struct {
db *db.DB
}
func NewBlogAPI(db *db.DB) *BlogAPI {
return &BlogAPI{db: db}
}
func (b *BlogAPI) ListPosts(c *gin.Context) {
posts, err := b.db.ListPosts()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
apiPosts := make([]api.Post, len(posts))
for i, post := range posts {
apiPosts[i] = api.Post{
Id: post.ID.Hex(),
Title: post.Title,
Content: post.Content,
CreatedAt: post.CreatedAt,
UpdatedAt: post.UpdatedAt,
}
}
c.JSON(http.StatusOK, apiPosts)
}
func (b *BlogAPI) CreatePost(c *gin.Context) {
var newPost api.NewPost
if err := c.ShouldBindJSON(&newPost); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
post, err := b.db.CreatePost(newPost.Title, newPost.Content)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, api.Post{
Id: post.ID.Hex(),
Title: post.Title,
Content: post.Content,
CreatedAt: post.CreatedAt,
UpdatedAt: post.UpdatedAt,
})
}
func (b *BlogAPI) GetPost(c *gin.Context) {
id := c.Param("id")
post, err := b.db.GetPost(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"})
return
}
c.JSON(http.StatusOK, api.Post{
Id: post.ID.Hex(),
Title: post.Title,
Content: post.Content,
CreatedAt: post.CreatedAt,
UpdatedAt: post.UpdatedAt,
})
}
func (b *BlogAPI) UpdatePost(c *gin.Context) {
id := c.Param("id")
var updatePost api.NewPost
if err := c.ShouldBindJSON(&updatePost); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
post, err := b.db.UpdatePost(id, updatePost.Title, updatePost.Content)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"})
return
}
c.JSON(http.StatusOK, api.Post{
Id: post.ID.Hex(),
Title: post.Title,
Content: post.Content,
CreatedAt: post.CreatedAt,
UpdatedAt: post.UpdatedAt,
})
}
func (b *BlogAPI) DeletePost(c *gin.Context) {
id := c.Param("id")
err := b.db.DeletePost(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"})
return
}
c.Status(http.StatusNoContent)
}
運行應用程式
在專案根目錄中建立一個名為 main.go 的新檔案來設定和執行應用程式:
package main
import (
"log"
"github.com/gin-gonic/gin"
"github.com/yourusername/blog-api/api"
"github.com/yourusername/blog-api/db"
"github.com/yourusername/blog-api/handlers"
)
func main() {
// Initialize the database connection
database, err := db.NewDB("mongodb://localhost:27017")
if err != nil {
log.Fatalf("Failed to connect to the database: %v", err)
}
defer database.Close()
// Create a new Gin router
router := gin.Default()
// Initialize the BlogAPI handlers
blogAPI := handlers.NewBlogAPI(database)
// Register the API routes
api.RegisterHandlers(router, blogAPI)
// Start the server
log.Println("Starting server on :8080")
if err := router.Run(":8080"); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
}
測試 API
現在我們已經啟動並運行了 API,讓我們使用curl 命令對其進行測試:
- 建立一個新貼文:
curl -X POST -H "Content-Type: application/json" -d '{"title":"My First Post","content":"This is the content of my first post."}' http://localhost:8080/posts
- 列出所有貼文:
curl http://localhost:8080/posts
- 取得特定貼文(將 {id} 替換為實際貼文 ID):
curl http://localhost:8080/posts/{id}
- 更新貼文(將 {id} 替換為實際貼文 ID):
curl -X PUT -H "Content-Type: application/json" -d '{"title":"Updated Post","content":"This is the updated content."}' http://localhost:8080/posts/{id}
- 刪除貼文(將 {id} 替換為實際貼文 ID):
curl -X DELETE http://localhost:8080/posts/{id}
結論
在本教程中,我們使用 Gin 框架、FerretDB 和 oapi-codegen 建立了一個簡單的部落格 API。我們已經介紹了以下步驟:
- 設定專案並安裝依賴項
- 使用 OpenAPI 3.0 定義 API 規格
- 使用 oapi-codegen 產生伺服器程式碼
- 使用FerretDB實作資料庫層
- 實作 API 處理程序
- 運行應用程式
- 使用curl指令測試API
專案示範如何利用程式碼產生和 MongoDB 相容資料庫的強大功能,使用 Go 建立 RESTful API。您可以透過新增身份驗證、分頁和更複雜的查詢功能來進一步擴充此 API。
請記住在將此 API 部署到生產環境之前適當處理錯誤、新增適當的日誌記錄並實施安全措施。
需要幫助嗎?
您是否面臨著具有挑戰性的問題,或需要外部視角來看待新想法或專案?我可以幫忙!無論您是想在進行更大投資之前建立技術概念驗證,還是需要解決困難問題的指導,我都會為您提供協助。
提供的服務:
- 解決問題:透過創新的解決方案解決複雜問題。
- 諮詢:為您的專案提供專家建議和新觀點。
- 概念驗證:開發初步模型來測試和驗證您的想法。
如果您有興趣與我合作,請透過電子郵件與我聯繫:hungaikevin@gmail.com。
讓我們將挑戰轉化為機會!
以上是使用 Gin、FerretDB 和 oapi-codegen 建立部落格 API的詳細內容。更多資訊請關注PHP中文網其他相關文章!
熱AI工具
Undress AI Tool
免費脫衣圖片
Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片
AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。
Clothoff.io
AI脫衣器
Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!
熱門文章
熱工具
記事本++7.3.1
好用且免費的程式碼編輯器
SublimeText3漢化版
中文版,非常好用
禪工作室 13.0.1
強大的PHP整合開發環境
Dreamweaver CS6
視覺化網頁開發工具
SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)
如何在GO中的結構實例上調用方法?
Jun 24, 2025 pm 03:17 PM
在Go語言中,調用結構體方法需先定義結構體和綁定接收者的方法,使用點號訪問。定義結構體Rectangle後,可通過值接收者或指針接收者聲明方法;1.使用值接收者如func(rRectangle)Area()int,通過rect.Area()直接調用;2.若需修改結構體,應使用指針接收者如func(r*Rectangle)SetWidth(...),Go會自動處理指針與值的轉換;3.嵌入結構體時,內嵌結構體的方法會被提升,可直接通過外層結構體調用;4.Go無需強制使用getter/setter,字
GO中的接口是什麼?如何定義它們?
Jun 22, 2025 pm 03:41 PM
在Go語言中,接口是一種定義行為而不指定實現方式的類型。接口由方法簽名組成,任何實現這些方法的類型都自動滿足該接口。例如,定義一個Speaker接口包含Speak()方法,則所有實現該方法的類型均可視為Speaker。接口適用於編寫通用函數、抽象實現細節和測試中使用mock對象。定義接口使用interface關鍵字並列出方法簽名,無需顯式聲明類型實現了接口。常見用例包括日誌、格式化、不同數據庫或服務的抽象,以及通知系統等。例如,Dog和Robot類型均可實現Speak方法,並傳遞給同一個Anno
將Golang服務與現有Python基礎架構集成的策略
Jul 02, 2025 pm 04:39 PM
TOIntegrategolangServicesWithExistingPypythoninFrasture,userestapisorgrpcForinter-serviceCommunication,允許GoandGoandPyThonAppStoStoInteractSeamlessSeamLlyThroughlyThroughStandArdArdAdrotized Protoccols.1.usererestapis(ViaFrameWorkslikeSlikeSlikeGiningOandFlaskInpyThon)Orgrococo(wirs Propococo)
我如何使用時間軟件包來處理GO的時間和持續時間?
Jun 23, 2025 pm 11:21 PM
Go的time包提供了處理時間和持續時間的功能,包括獲取當前時間、格式化日期、計算時間差、處理時區、調度和休眠等操作。要獲取當前時間,使用time.Now()獲取Time結構體,並可通過Year()、Month()、Day()等方法提取具體時間信息;通過Format("2006-01-0215:04:05")可將時間格式化為字符串;計算時間差時,用Sub()或Since()獲取Duration對象,再通過Seconds()、Minutes()、Hours()轉換為對應單位;添
我如何根據語句使用語句執行代碼?
Jun 23, 2025 pm 07:02 PM
Ingo,ifstatementSexecuteCodeBasedonConconditions.1.BasicsStructurerunsablockifaconditionistrue,例如IFX> 10 {...}。 2.Elseclausehan dlesfalseconditions,例如,else {...}。 3。 elseifchainsmultipleconditions,例如,elseifx == 10 {...}。 4.variableInitializationInsideIndifif,l
了解Web API的Golang和Python之間的性能差異
Jul 03, 2025 am 02:40 AM
Golangofferssuperiorperformance,nativeconcurrencyviagoroutines,andefficientresourceusage,makingitidealforhigh-traffic,low-latencyAPIs;2.Python,whileslowerduetointerpretationandtheGIL,provideseasierdevelopment,arichecosystem,andisbettersuitedforI/O-bo
如何使用lock()和unlock()方法來保護GO中的重要代碼部分?
Jun 23, 2025 pm 08:37 PM
在Go中保護臨界區的標準方法是使用sync.Mutex的Lock()和Unlock()方法。 1.聲明一個mutex並將其與要保護的數據一起使用;2.在進入臨界區前調用Lock(),確保只有一個goroutine能訪問共享資源;3.使用deferUnlock()確保鎖始終被釋放,避免死鎖;4.盡量縮短臨界區內的操作以提高性能;5.對於讀多寫少的場景,應使用sync.RWMutex,通過RLock()/RUnlock()進行讀操作,通過Lock()/Unlock()進行寫操作,從而提升並發效率。
去支持並發如何?
Jun 23, 2025 pm 12:37 PM
Gohandlesconcurrencyusinggoroutinesandchannels.1.GoroutinesarelightweightfunctionsmanagedbytheGoruntime,enablingthousandstorunconcurrentlywithminimalresourceuse.2.Channelsprovidesafecommunicationbetweengoroutines,allowingvaluestobesentandreceivedinas


