5chan API — Golang, GORM, Go-fiber

This is the continuation of the 5chan project.

Creating the backend:

  • Setup project and download dependencies in one Ctrl+v-Ctrl-v :
# Create the 5chan directory
mkdir 5chan-go
# Create main.go
touch main.go
# Init the go project
go mod init github.com/100lvlmaster/5chan-go
# Get go-fiber
go get github.com/gofiber/fiber/v2
# Get GORM
go get github.com/gofiber/fiber/v2
# Get air
curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
# Setup air
air -c .air.toml
# Get godotenv to read env vars [optional]

Create a directory structure like so:

controllers

  • posts.go
  • replies.go

database

  • database.go

middleware

  • middleware.go

models

  • post.go
  • reply.go

routes

  • routes.go
  • main.go

Da Code:

  • Creating structs

models/post.go

// models/post.go
package models
import "gorm.io/gorm"
type Post struct {
gorm.Model
ID string `gorm:"primaryKey"`
Title string `json:"title"`
Author string `json:"author"`
Body string `json:"body"`
Reply []Reply `json:"replies" gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}

models/reply.go

// models/reply.go
package models
import "gorm.io/gorm"
type Reply struct {
gorm.Model
ID string `gorm:"primaryKey"`
Author string `json:"author"`
Body string `json:"body"`
PostID string `json:"postId"`
Post Post `json:"post" gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
  • Connecting the databse and creating a singleton of the instance so we can use it everywhere.
// database/database.go
package database
import (
"fmt"
"os"
"github.com/100lvlmaster/5chan-go/models"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var (
// DBConn is a database connection singleton
DBConn *gorm.DB
)
func InitDb() *gorm.DB {
dsn, envExists := os.LookupEnv("DB_DSN")
if !envExists {
panic("Could not connect to the database")
}
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic("Could not initialize database")
}
migrate(db)
return db
}
func migrate(db *gorm.DB) {
err := db.AutoMigrate(&models.Post{}, &models.Reply{})
if err != nil {
fmt.Print("Could not init database")
return
}
fmt.Print("Database migrated")
}
  • Creating Controllers

controllers/posts.go

// controllers/posts.go
package controllers
import (
"github.com/100lvlmaster/5chan-go/database"
"github.com/100lvlmaster/5chan-go/models"
"github.com/gofiber/fiber/v2"
gonanoid "github.com/matoous/go-nanoid/v2"
)
func GetPosts(c *fiber.Ctx) error {
var posts []models.Post
database.DBConn.Find(&posts)
return c.JSON(posts)
}
func GetPostById(c *fiber.Ctx) error {
id := c.Params("id")
var post models.Post
database.DBConn.Preload("Reply").First(&post, id)
return c.JSON(post)
}
func CreatePost(c *fiber.Ctx) error {
var post models.Post
if err := c.BodyParser(&post); err != nil {
return c.Status(400).SendString(err.Error())
}
id, _ := gonanoid.Generate("0123456789", 10)
post.ID = id
database.DBConn.Create(&post)
return c.Status(201).JSON(post)
}
func DeletePost(c *fiber.Ctx) error {
id := c.Params("id")
var post models.Post
database.DBConn.Delete(&post, id)
return c.Status(200).SendString("Post deleted successfully")
}

controllers/replies.go

package controllersimport (
"errors"
"github.com/100lvlmaster/5chan-go/database"
"github.com/100lvlmaster/5chan-go/models"
"github.com/gofiber/fiber/v2"
gonanoid "github.com/matoous/go-nanoid/v2"
"gorm.io/gorm"
)
func GetReplies(c *fiber.Ctx) error {
var replies []models.Reply
database.DBConn.Find(&replies)
return c.JSON(replies)
}
///
func GetRepliesByPostId(c *fiber.Ctx) error {
id := c.Params("id")
var replies []models.Reply
database.DBConn.Where(&models.Reply{PostID: id}).Find(&replies)
return c.JSON(replies)
}
///
func CreateReply(c *fiber.Ctx) error {
var reply models.Reply
if err := c.BodyParser(&reply); err != nil {
return c.Status(400).SendString("Incorrect input body, please chcek input convention and try again")
}
/// DB contains post
var result models.Post
err := database.DBConn.First(&result, reply.PostID).Error
hasRecord := !errors.Is(err, gorm.ErrRecordNotFound)
if !hasRecord {
return c.Status(400).SendString("Post does not exist")
}
///
id, _ := gonanoid.Generate("0123456789", 10)
reply.ID = id
database.DBConn.Create(&reply)
return c.Status(201).JSON(reply)
}
  • Setup routes:

routes/routes.go

// routes/routes.go
package routes
import (
"github.com/100lvlmaster/5chan-go/controllers"
"github.com/gofiber/fiber/v2"
)
func RoutesInit(app *fiber.App) {
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hellso, World 👋!")
})
api := app.Group("/api")
v1 := api.Group("/v1")
v1.Get("/posts", controllers.GetPosts)
v1.Post("/posts", controllers.CreatePost)
v1.Get("/posts/:id", controllers.GetPostById)
v1.Delete("/posts/:id", controllers.DeletePost)
v1.Get("/replies", controllers.GetReplies)
v1.Get("/replies/:id", controllers.GetRepliesByPostId)
v1.Post("/replies", controllers.CreateReply)
}
  • A middleware to limit other methods like POST & DELETE to our Frontend. But you're using this you may change them to your liking in the .env file

middleware/middleware.go

package middlewareimport (
"os"
"github.com/gofiber/fiber/v2"
)
/// Limit post requests to the official client
func CustomeMiddleware() func(*fiber.Ctx) error {
return func(c *fiber.Ctx) error {
if c.Method() == "POST" || c.Method() == "DELETE" {
apiKey, envExists := os.LookupEnv("API_KEY")
if !envExists {
return c.Status(500).SendString("Could not fetch env vars, call 100lvlmaster")
}
key := c.Get("Authorization")
if apiKey != key {
return c.Status(400).SendString("Your keys are incorrect peasant ✨✨")
}
}
return c.Next()
}
}
  • And then tie everything together in the main.go file

main.go

// main.go
package main
import (
"os"
"github.com/100lvlmaster/5chan-go/database"
"github.com/100lvlmaster/5chan-go/middleware"
"github.com/100lvlmaster/5chan-go/routes"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/joho/godotenv"
)
func main() {
app := fiber.New()
godotenv.Load()
app.Use(cors.New())
// app.Use(cache.New())
app.Use(middleware.CustomeMiddleware())
database.DBConn = database.InitDb()
routes.RoutesInit(app)
port, envExists := os.LookupEnv("PORT")
if !envExists {
port = "8080"
}
app.Listen(":" + port)
}
  • And then run the project using:
air

After making some test requests these are the screenshots:

The source code of this project lies at:

https://github.com/100lvlmaster/5chan-go

You can find me at:
Website
https://100lvlmaster.in
Github
https://github.com/100lvlmaster

--

--

--

onw to 10x developer

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Debugging CodeDeploy Health_Constraints error

Solution to the k-closest point in space problem

Pet (Project) #1: Python

Upgrade Magento Version from 2.3.X to 2.4.X

What’s up in your BEAM? — introducing BEAMchmark

[iOS] Handling API Response with Decodable

Palindrome checker is a better basic example of test-driven development

Barman: Delete Backup

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Navin Kodag

Navin Kodag

onw to 10x developer

More from Medium

Using GitLab Private Go Package On Your Local Setup and CI 🚀

JWT tokens & Go (Golang): Easy explanation

DynamoDb with AWS SDK Go v2 — Part 1

REST API service boilerplate using Gin web framework, Golang

GitHub repository preview