Introduction
We know the best way to learn is to build stuff while learning, so I got into building a RESTful API to manage blog posts. It’s a simple project that lets you create, read, update, and delete posts—basically, the classic CRUD operations. I chose Go because I'm familiar with it,😂😂😎😂! It's a great language for this kind of project, and it makes the whole process smoother since I already know my way around it. Plus, Go’s performance is top-notch, which makes it perfect for handling API requests quickly.
1. Overview
In this tutorial, we’ll walk through the entire process of creating a simple RESTful API in Go to manage blog posts. The API will allow us to perform the basic CRUD operations: Create, Read, Update, and Delete posts. We'll structure the application with a few simple files to keep things clear.
2. Our Entry Point
FILE: main.go
package main
import (
"log"
"net/http"
"your_project/route" // import your route package
)
func main() {
r := route.NewRouter() // initialize the router
log.Fatal(http.ListenAndServe(":8080", r)) // start the server
}
In this file, we set up the HTTP server and the routes using the mux
router. The server listens on port 8080
, and we define all the routes inside the route.NewRouter()
function.
3. Post Model
FILE: model.go
package model
type Post struct {
Title string
Content string
Tags []string
Author string
}
This Post
struct is our model, which represents a blog post. It contains fields for Title
, Content
, Tags
, and Author
. Each post will be stored in memory with these fields.
4. Setting Up Routes for CRUD Operations
FILE: route.go
package route
import (
"github.com/gorilla/mux"
"your_project/handler"
"your_project/middleware"
)
func NewRouter() *mux.Router {
r := mux.NewRouter()
// create a post
r.HandleFunc("/create", handler.CreatePost).Methods("POST")
// list the titles of all posts
r.HandleFunc("/list", handler.ListPosts).Methods("GET")
// get a post by the post title
r.HandleFunc("/post", handler.GetPostByTitle).Methods("GET")
// edit a post
r.HandleFunc("/edit", handler.UpdatePost).Methods("PUT")
// delete a blog post
r.HandleFunc("/delete", middleware.RequireAuth(handler.DeletePost)).Methods("DELETE")
return r
}
Here, we define all the routes for the CRUD operations:
-
Create Post:
POST /create
-
List All Posts:
GET /posts
-
Get Post by Title:
GET /blogs
-
Update Post:
PUT /edit
-
Delete Post:
DELETE /delete
5. Implementing the Handlers
FILE: handler.go
package handler
import (
"encoding/json"
"fmt"
"net/http"
"your_project/model"
)
// a in-built memory database to store and retrieve our posts.
var allPosts = make(map[string]model.Post)
- Create Post(POST)
func CreatePost(w http.ResponseWriter, r *http.Request) {
var post model.Post
// read content from request body into a new decoder
decoder := json.NewDecoder(r.Body)
// decode content into our Post struct
err := decoder.Decode(&post)
if err != nil {
http.Error(w, "Failed to decode request body", http.StatusInternalServerError)
return
}
// check for unique post title
_, ok := allPosts[post.Title]
if ok {
http.Error(w, "Post title already exists", http.StatusBadRequest)
return
}
// append post to our memory
allPosts[post.Title] = post
// prints out structs with field names
fmt.Fprintf(w, "%+v", post)
}
- Get Post(GET)
func ListPosts(w http.ResponseWriter, r *http.Request) {
titles := []string{}
for _, post := range allPosts {
titles = append(titles, post.Title)
}
if len(titles) == 0 {
http.Error(w, "no posts found", http.StatusNotFound)
}
json.NewEncoder(w).Encode(titles)
// uncomment to print out structs with field names
// fmt.Fprintf(w,"%+v",titles)
}
- Get Post by Title(GET)
func GetPostByTitle(w http.ResponseWriter, r *http.Request) {
// retrieve title of post
title := r.URL.Query().Get("title")
if title == "" {
http.Error(w, "Title is required", http.StatusBadRequest)
return
}
// check title is present
post, ok := allPosts[title]
if !ok {
http.Error(w, "Post not found", http.StatusNotFound)
return
}
// encode the contents
if err := json.NewEncoder(w).Encode(post); err != nil {
http.Error(w, "Post not found", http.StatusInternalServerError)
return
}
}
- Update Post(PUT)
func UpdatePost(w http.ResponseWriter, r *http.Request) {
// get title
// check title is provided
title := r.URL.Query().Get("title")
if title == "" {
http.Error(w, "Title is required", http.StatusBadRequest)
return
}
// check if such post exists
post, ok := allPosts[title]
if !ok {
http.Error(w, "BlogPost not found", http.StatusNotFound)
return
}
var updatedPost model.Post
// read request body
if err := json.NewDecoder(r.Body).Decode(&updatedPost); err != nil {
http.Error(w, "Failed to decode request body", http.StatusBadRequest)
return
}
// update post
allPosts[title] = updatedPost
post = updatedPost
// return ok status
w.WriteHeader(http.StatusOK)
// return updated content
json.NewEncoder(w).Encode(post)
}
- Delete Post(Delete)
func DeletePost(w http.ResponseWriter, r *http.Request) {
title := r.URL.Query().Get("title")
// retrieve post
_, ok := allPosts[title]
if !ok {
http.Error(w, "No post with such title", http.StatusNotFound)
}
// deletes the post from the map.
delete(allPosts, title)
w.WriteHeader(http.StatusOK)
}
These handler functions handle the logic for each CRUD operation. They read the request body (for POST
and PUT
requests), interact with the in-memory database (allPosts
), and send responses accordingly.
6. Middleware (Authentication)
FILE: auth.go
package middleware
import "net/http"
func RequireAuth(f http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// check if user is authenticated
token := r.Header.Get("Authorization")
if token != "Bearer Secret" {
http.Error(w,"Unathorized", http.StatusUnauthorized)
return
}
f.ServeHTTP(w,r)
})
}
This middleware function checks for a valid Authorization header. If the token isn’t Bearer Secret
, it returns an unauthorized response.
7.Run the Aplication
Run the following commands to initialize and tidy up dependencies:
run to install.
cd your_project
go mod init your_project
go mod tidy
8. Testing our API
Once the API is up and running, you can test the endpoints using curl commands.
Run the server:
go run main.go
Using curl
Then, in another terminal window, use curl
to test:
- Create Post:
curl -X POST -H "Content-Type: application/json" -d '{"title":"My First Post", "content":"This is the content.", "author":"John Doe"}' http://localhost:8080/create
- Get Post by Post Title:
curl -X GET 'http://localhost:8080/post?title=My%20First%20Post'
- Get All Posts Titles:
curl http://localhost:8080/list
- Update Post
curl -X PUT -H "Content-Type: application/json" -d '{"Title":"My First Post", "Content":"Updated content.", "Author":"John Doe"}' 'http://localhost:8080/edit?title=My%20Blog%20Post'
- Delete Post
curl -X DELETE -H "Authorization: Bearer Secret" 'http://localhost:8080/delete?title=My%20First%20Post'
Using Thunder Client
Thunder Client is a lightweight and powerful API testing tool, typically used within Visual Studio Code (VS Code). It offers a user-friendly interface to make API requests.
Check out how to use Thunder Client to make API requests :
Conclusion
And there you have it—your very own RESTful API for managing blog posts, built with the power of Go! 🚀. From creating posts to updating, deleting, and viewing them.
But wait—Want to add user authentication, store data in a database, or make your API even more scalable? Go for it! 🔥
Top comments (2)
Great article. I found it helpful while implementing an API of my own, most especially, the testing part using curl.
Glad it was helpful, Antony! Command-line curl truly embodies the developer spirit. 🚀