0

0

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

WBOY

WBOY

发布时间:2024-09-05 22:30:44

|

1054人浏览过

|

来源于dev.to

转载

使用 gin、ferretdb 和 oapi-codegen 构建博客 api

在本教程中,我们将逐步介绍使用 go 为简单博客应用程序创建 restful api 的过程。我们将使用以下技术:

  1. gin:go 的 web 框架
  2. ferretdb:兼容 mongodb 的数据库
  3. oapi-codegen:根据 openapi 3.0 规范生成 go 服务器样板的工具

目录

  1. 设置项目
  2. 定义 api 规范
  3. 生成服务器代码
  4. 实现数据库层
  5. 实现 api 处理程序
  6. 运行应用程序
  7. 测试 api
  8. 结论

设置项目

首先,让我们设置 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 的新文件来设置和运行应用程序:

Lifetoon
Lifetoon

免费的AI漫画创作平台

下载
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 命令对其进行测试:

  1. 创建一个新帖子:
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. 列出所有帖子:
curl http://localhost:8080/posts

  1. 获取特定帖子(将 {id} 替换为实际帖子 id):
curl http://localhost:8080/posts/{id}

  1. 更新帖子(将 {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}

  1. 删除帖子(将 {id} 替换为实际帖子 id):
curl -X DELETE http://localhost:8080/posts/{id}

结论

在本教程中,我们使用 gin 框架、ferretdb 和 oapi-codegen 构建了一个简单的博客 api。我们已经介绍了以下步骤:

  1. 设置项目并安装依赖项
  2. 使用 openapi 3.0 定义 api 规范
  3. 使用 oapi-codegen 生成服务器代码
  4. 使用ferretdb实现数据库层
  5. 实现 api 处理程序
  6. 运行应用程序
  7. 使用curl命令测试api

该项目演示了如何利用代码生成和 mongodb 兼容数据库的强大功能,使用 go 创建 restful api。您可以通过添加身份验证、分页和更复杂的查询功能来进一步扩展此 api。

请记住在将此 api 部署到生产环境之前适当处理错误、添加适当的日志记录并实施安全措施。


需要帮助吗?

您是否面临着具有挑战性的问题,或者需要外部视角来看待新想法或项目?我可以帮忙!无论您是想在进行更大投资之前建立技术概念验证,还是需要解决困难问题的指导,我都会为您提供帮助。

提供的服务:

  • 解决问题:通过创新的解决方案解决复杂问题。
  • 咨询:为您的项目提供专家建议和新观点。
  • 概念验证:开发初步模型来测试和验证您的想法。

如果您有兴趣与我合作,请通过电子邮件与我联系:hungaikevin@gmail.com。

让我们将挑战转化为机遇!

相关专题

更多
PHP API接口开发与RESTful实践
PHP API接口开发与RESTful实践

本专题聚焦 PHP在API接口开发中的应用,系统讲解 RESTful 架构设计原则、路由处理、请求参数解析、JSON数据返回、身份验证(Token/JWT)、跨域处理以及接口调试与异常处理。通过实战案例(如用户管理系统、商品信息接口服务),帮助开发者掌握 PHP构建高效、可维护的RESTful API服务能力。

145

2025.11.26

curl_exec
curl_exec

curl_exec函数是PHP cURL函数列表中的一种,它的功能是执行一个cURL会话。给大家总结了一下php curl_exec函数的一些用法实例,这个函数应该在初始化一个cURL会话并且全部的选项都被设置后被调用。他的返回值成功时返回TRUE, 或者在失败时返回FALSE。

423

2023.06.14

linux常见下载安装工具
linux常见下载安装工具

linux常见下载安装工具有APT、YUM、DNF、Snapcraft、Flatpak、AppImage、Wget、Curl等。想了解更多linux常见下载安装工具相关内容,可以阅读本专题下面的文章。

172

2023.10.30

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

989

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

50

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

225

2025.12.29

mongodb和mysql的区别
mongodb和mysql的区别

mongodb和mysql的区别:1、数据模型;2、查询语言;3、扩展性和性能;4、可靠性。本专题为大家提供mongodb和mysql的区别的相关的文章、下载、课程内容,供大家免费下载体验。

280

2023.07.18

mongodb启动命令
mongodb启动命令

MongoDB 是一种开源的、基于文档的 NoSQL 数据库管理系统。本专题提供mongodb启动命令的文章,希望可以帮到大家。

246

2023.08.08

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

65

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Git 教程
Git 教程

共21课时 | 2.3万人学习

Git版本控制工具
Git版本控制工具

共8课时 | 1.5万人学习

Git中文开发手册
Git中文开发手册

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号