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

Easy deployment with GitHub and AWS CodePipeline

Never❎ give 🙌 ;emoji story

LCA Learning Unit 3 — Week 1

Request Body Transformation in Spring Cloud Gateway

5 Steps to Create a Wordpress Site on AWS Free Tier

Project Name : 🚀Apeshit Social Airdrop!!!

It’s okay to be confused

Best of My Work

A clock, a coffee, and glasses

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

Web Sockets in Golang

GoFrame 101: Static file handler with Web UI

Authentication and Authorization (OpenID Connect)of Go Rest Api’s using an open-source IAM called…

Asgardeo authentication with Golang and Goth