DEV Community

Cover image for 5chan API - Golang, GORM, Go-fiber
Navin Kodag
Navin Kodag

Posted on

3 1

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]

Enter fullscreen mode Exit fullscreen mode

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

dir-struct.png

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;"`
}
Enter fullscreen mode Exit fullscreen mode

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;"`
}
Enter fullscreen mode Exit fullscreen mode
  • 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")
}
Enter fullscreen mode Exit fullscreen mode
  • 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")
}
Enter fullscreen mode Exit fullscreen mode

controllers/replies.go

package controllers

import (
"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)
}
Enter fullscreen mode Exit fullscreen mode
  • 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)
}
Enter fullscreen mode Exit fullscreen mode
  • 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 middleware

import (
"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()
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 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)
}
Enter fullscreen mode Exit fullscreen mode
  • And then run the project using:
air
Enter fullscreen mode Exit fullscreen mode

air.png

After making some test requests these are the screenshots:
test-1.png
test-2.png

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

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more