”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 使用 Gin、FerretDB 和 oapi-codegen 构建博客 API

使用 Gin、FerretDB 和 oapi-codegen 构建博客 API

发布于2024-10-31
浏览:116

Building a Blog API with Gin, FerretDB, and oapi-codegen

In this tutorial, we’ll walk through the process of creating a RESTful API for a simple blog application using Go. We’ll be using the following technologies:

  1. Gin: A web framework for Go
  2. FerretDB: A MongoDB-compatible database
  3. oapi-codegen: A tool for generating Go server boilerplate from OpenAPI 3.0 specifications

Table of Contents

  1. Setting Up the Project
  2. Defining the API Specification
  3. Generating Server Code
  4. Implementing the Database Layer
  5. Implementing the API Handlers
  6. Running the Application
  7. Testing the API
  8. Conclusion

Setting Up the Project

First, let’s set up our Go project and install the necessary dependencies:

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

Defining the API Specification

Create a file named api.yaml in your project root and define the OpenAPI 3.0 specification for our blog API:

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

Generating Server Code

Now, let’s use oapi-codegen to generate the server code based on our API specification:

oapi-codegen -package api api.yaml > api/api.go

This command will create a new directory called api and generate the api.go file containing the server interfaces and models.

Implementing the Database Layer

Create a new file called db/db.go to implement the database layer using 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
}

Implementing the API Handlers

Create a new file called handlers/handlers.go to implement the API handlers:

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)
}

Running the Application

Create a new file called main.go in the project root to set up and run the application:

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)
    }
}

Testing the API

Now that we have our API up and running, let’s test it using curl commands:

  1. Create a new post:
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

  1. List all posts:
curl http://localhost:8080/posts

  1. Get a specific post (replace {id} with the actual post ID):
curl http://localhost:8080/posts/{id}

  1. Update a post (replace {id} with the actual post ID):
curl -X PUT -H "Content-Type: application/json" -d '{"title":"Updated Post","content":"This is the updated content."}' http://localhost:8080/posts/{id}

  1. Delete a post (replace {id} with the actual post ID):
curl -X DELETE http://localhost:8080/posts/{id}

Conclusion

In this tutorial, we’ve built a simple blog API using the Gin framework, FerretDB, and oapi-codegen. We’ve covered the following steps:

  1. Setting up the project and installing dependencies
  2. Defining the API specification using OpenAPI 3.0
  3. Generating server code with oapi-codegen
  4. Implementing the database layer using FerretDB
  5. Implementing the API handlers
  6. Running the application
  7. Testing the API with curl commands

This project demonstrates how to create a RESTful API with Go, leveraging the power of code generation and a MongoDB-compatible database. You can further extend this API by adding authentication, pagination, and more complex querying capabilities.

Remember to handle errors appropriately, add proper logging, and implement security measures before deploying this API to a production environment.


Need Help?

Are you facing challenging problems, or need an external perspective on a new idea or project? I can help! Whether you're looking to build a technology proof of concept before making a larger investment, or you need guidance on difficult issues, I'm here to assist.

Services Offered:

  • Problem-Solving: Tackling complex issues with innovative solutions.
  • Consultation: Providing expert advice and fresh viewpoints on your projects.
  • Proof of Concept: Developing preliminary models to test and validate your ideas.

If you're interested in working with me, please reach out via email at [email protected].

Let's turn your challenges into opportunities!

版本声明 本文转载于:https://dev.to/hungai/building-a-blog-api-with-gin-ferretdb-and-oapi-codegen-30o9?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 掌握 React:构建强大 Web 应用程序的循序渐进之旅(简介)
    掌握 React:构建强大 Web 应用程序的循序渐进之旅(简介)
    React is a popular JavaScript library used to build user interfaces, especially for single-page websites or apps. Whether you're a complete beginner o...
    编程 发布于2024-11-08
  • JavaScript DOM 与 BOM!
    JavaScript DOM 与 BOM!
    DOM DOM 代表文档对象模型,代表网页。这允许程序操纵文档结构、样式和内容。 const listDiv = document.getElementById("list-div"); listDiv.classList.add('new-class'); listDiv.cla...
    编程 发布于2024-11-08
  • 绑定和模板:Peasy-UI 系列的一部分
    绑定和模板:Peasy-UI 系列的一部分
    Table of Contents Introduction Bindings and the Template Text Bindings Basic Binding Conditional Boolean Text B...
    编程 发布于2024-11-08
  • 实现接口
    实现接口
    定义接口后,一个或多个类可以实现它。 要实现接口,请在类定义中使用 Implements 子句。 该类必须实现接口所需的所有方法。 包含 Implements 子句的类的一般形式是: 类类名扩展超类实现接口{ // 类体 } 若要实现多个接口,接口之间用逗号分隔。 实现接口时,extend...
    编程 发布于2024-11-08
  • 检查 Effect-TS 选项中的元素:实用指南
    检查 Effect-TS 选项中的元素:实用指南
    Effect-TS 提供了检查 Option 是否包含特定值的方法。这些函数允许您使用自定义等价函数或默认等价来确定选项中是否存在值。在本文中,我们将探讨用于检查选项中元素的两个关键函数:O.containsWith 和 O.contains. 示例 1:使用 O.containsW...
    编程 发布于2024-11-08
  • Python 面向对象编程简介
    Python 面向对象编程简介
    Python 编程语言 Python 是一种解释型、面向对象的编程语言。由于其高级内置数据结构和动态类型,它在快速开发新应用程序以及编写脚本代码以组合用不同语言编写的现有组件方面很受欢迎。 Python简单易学的语法强调可读性,从而降低了长期程序维护的成本和复杂性。它支持各种包含代...
    编程 发布于2024-11-08
  • 最佳软件比较中的顶级数据科学工具
    最佳软件比较中的顶级数据科学工具
    介绍 到 2024 年,数据科学将通过使用复杂的分析、人工智能和机器学习推动决策,继续改变业务。随着对熟练数据科学家的需求不断增加,对能够加快操作、提高生产力并提供可靠见解的强大工具的需求也在增加。但是,有这么多可用的选项,目前哪种软件最适合专业人士? 这项比较研究探讨了 2024...
    编程 发布于2024-11-08
  • 我如何将应用程序性能提高到
    我如何将应用程序性能提高到
    ⌛ 回顾时间 在我的上一篇博客中,我谈到了如何在短短 2 周内将应用程序大小从 75MB 减少到 34MB(查看!)。但这还不是全部,我还将我们应用程序的整体性能提高了 80%?. 让我们来看看如何!! ?传说 经过简单的一轮浏览后,我发现我们的应用程序中存在一些导...
    编程 发布于2024-11-08
  • Django 查询集可以通过模型属性过滤吗?
    Django 查询集可以通过模型属性过滤吗?
    按模型属性过滤 Django 查询集Django 模型上的查询通常使用标准过滤器根据预定义字段值选择特定实例。但是,如果您需要根据模型中定义的自定义属性进行过滤,该怎么办?您可以通过模型属性过滤查询集吗?不幸的是,Django 的过滤器主要运行在数据库级别,将它们转换为 SQL 命令以有效地检索数据...
    编程 发布于2024-11-08
  • 尽管配置正确,为什么我无法在 Laravel 中发送 TLS 电子邮件?
    尽管配置正确,为什么我无法在 Laravel 中发送 TLS 电子邮件?
    无法发送 TLS 电子邮件:解决 Laravel 证书验证错误尽管启用了不太安全的 Gmail 设置并正确配置了 Laravel 的 .env 文件,您在发送 TLS 电子邮件时遇到证书验证失败。错误消息表明 SSL 操作失败并且无法验证服务器证书。要解决此问题,如果您的操作系统没有自动管理受信任的...
    编程 发布于2024-11-08
  • 使用 Wasmtime 和 Wasm3 将 Golang 编译为 Wasm 时出现错误如何解决?
    使用 Wasmtime 和 Wasm3 将 Golang 编译为 Wasm 时出现错误如何解决?
    使用 Wasmtime 和 Wasm3 将 Golang 编译为 Wasm 时出现错误使用 GOOS=js 将 Golang 代码编译为 WebAssembly (Wasm) GOARCH=wasm go使用 Wasmtime 或 Wasm3 执行时,build -o main.wasm 可能会导致...
    编程 发布于2024-11-08
  • 如何访问 Iframe 的当前位置?
    如何访问 Iframe 的当前位置?
    访问 iframe 的当前位置:挑战和解决方法跨源资源共享 (CORS) 法规在尝试检索 iframe 时带来了重大挑战iframe 的当前位置。此安全措施可防止驻留在不同来源的 JavaScript 代码直接访问页面的 URL。虽然使用 JavaScript 访问 iframe 的 URL 不可行...
    编程 发布于2024-11-08
  • Spring Security 与 JWT
    Spring Security 与 JWT
    In this article, we will explore how to integrate Spring Security with JWT to build a solid security layer for your application. We will go through ea...
    编程 发布于2024-11-08
  • Google Sheets:如何花费数小时构建 SUMIFS
    Google Sheets:如何花费数小时构建 SUMIFS
    大家好!今天我想分享一个我创建的超级有用的脚本,用于解决日常生活中的常见问题。 如果您曾经尝试在 Google 表格中对“持续时间”求和,您可能已经注意到,SUMIF 和 SUMIFS 公式无法根据特定条件对事件或产品的持续时间求和。根据您需要执行的计算类型,这可能会成为一个障碍。但别担心! Goo...
    编程 发布于2024-11-08
  • WordPress 迁移插件终极指南
    WordPress 迁移插件终极指南
    迁移 WordPress 网站就像收拾房子搬到新房子一样。确保所有内容(内容、主题、插件、媒体文件甚至数据库)完美移动且没有任何损坏的挑战似乎令人望而生畏。但就像搬家公司让搬家变得更容易一样,WordPress 迁移插件简化了将网站从一台主机移动到另一台主机的复杂过程。 无论您是切换主机、从本地开发...
    编程 发布于2024-11-08

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3