
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:
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
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
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.
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
}
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)
}
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)
}
}
Now that we have our API up and running, let’s test it using curl commands:
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
curl http://localhost:8080/posts/{id}
curl -X PUT -H "Content-Type: application/json" -d '{"title":"Updated Post","content":"This is the updated content."}' http://localhost:8080/posts/{id}
curl -X DELETE http://localhost:8080/posts/{id}
In this tutorial, we’ve built a simple blog API using the Gin framework, FerretDB, and oapi-codegen. We’ve covered the following steps:
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.
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.
If you're interested in working with me, please reach out via email at hungaikevin@gmail.com.
Let's turn your challenges into opportunities!
The above is the detailed content of Building a Blog API with Gin, FerretDB, and oapi-codegen. For more information, please follow other related articles on the PHP Chinese website!