在本教程中,我们将逐步介绍使用 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。
让我们将挑战转化为机遇!