Hey, I'm Shivam, I develop WebApps using NodeJs, Golang and sometimes Django too. Here I'm writing about how to use Gin and res-api-framework in Golang. Gin in popular golang frame work because of it's built-in fuctionality.
- What This Part Will Cover:
- Introduction to the series and what the final app will do
- Project setup (Go modules, folder structure)
- Installing Gin and other necessary packages
- Setting up SQLite with GORM
- Creating models (User, maybe Post as a preview)
- Implementing user signup and login with password hashing
- Basic testing using Postman or curl
Introduction
In this series I will explain everyting from setup to implementation, for coding part I will post in 3 parts and for explaination of code it will be a single and last part. this is a simple blog app where user can post, update and delete their post.
Project setup
create a folder stucture like this:
gin-blog-api/
│
├── go.mod
├── go.sum
├── main.go
│
├── .env
│
├── config/
│ └── db.go
│
├── middleware/
│ └── middleware.go
│
├── models/
│ └── user.go
│
├── handlers/
│ └── auth.go
│
├── routes/
│ └── routes.go
│
├── utils/
│ └── jwt.go
initialize go module
go mod init gin-blog-api
Installing Gin and other necessary packages
go get github.com/gin-gonic/gin
go get gorm.io/gorm
go get gorm.io/driver/sqlite
go get golang.org/x/crypto/bcrypt
Setting up SQLite with GORM
add followig in config/database.go
package config
import (
"gin-blog-api/models"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
var DB *gorm.DB
func ConnectDatabase() {
database, err := gorm.Open(sqlite.Open("blog.db"), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database!", err)
}
err = database.AutoMigrate(&models.Post{})
if err != nil {
log.Fatal("Migration failed!", err)
}
database.AutoMigrate(&models.Post{}, &models.User{})
DB = database
}
Creating models
add following in models/users.go:
type User struct {
ID uint `gorm:"primaryKey"`
Username string `gorm:"unique"`
Email string `gorm:"unique"`
Password string `gorm:"not null"`
}
Implementing user signup and login with password hashing
add this in handlers/auth.go
package handlers
import (
"fmt"
"gin-blog-api/config"
"gin-blog-api/models"
"net/http"
"gin-blog-api/utils"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
)
func Signup(c *gin.Context) {
var user models.User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
fmt.Println("RAW password received:", user.Password)
if user.Password == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Password is required"})
return
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not hash password"})
return
}
user.Password = string(hashedPassword)
if err := config.DB.Create(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not create user"})
return
}
c.JSON(http.StatusCreated, gin.H{"message": "User created successfully"})
}
func Login(c *gin.Context) {
var loginData struct {
Email string `json:"email"`
Password string `json:"password"`
}
var user models.User
if err := c.ShouldBindJSON(&loginData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := config.DB.Where("email = ?", loginData.Email).First(&user).Error; err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"})
return
}
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(loginData.Password)); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"})
return
}
token, err := utils.GenerateJWT(user.ID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
return
}
c.SetCookie("token", token, 24*3600, "/", "localhost", false, true)
c.JSON(http.StatusOK, gin.H{
"token": token,
"user": gin.H{
"id": user.ID,
"username": user.Username,
"email": user.Email,
},
})
}
func Logout(c *gin.Context) {
// For stateless JWT authentication, logout is handled on the client side.
// Here we can just return a success message.
c.SetCookie("token", "", -1, "/", "localhost", false, true) // Clear the cookie
c.JSON(http.StatusOK, gin.H{"message": "Logged out successfully"})
}
creating JWT token
utils/jwt.go
package utils
import (
"fmt"
"log"
"os"
"time"
"github.com/golang-jwt/jwt/v5"
)
func GenerateJWT(userID uint) (string, error) {
claims := jwt.MapClaims{
"user_id": userID,
"exp": time.Now().Add(24 * time.Hour).Unix(), // 1 day expiry
}
log.Println("JWT_SECRET during sign:", os.Getenv("JWT_SECRET"))
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(os.Getenv("JWT_SECRET")))
}
func ParseJWT(tokenStr string) (uint, error) {
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("JWT_SECRET")), nil
})
if err != nil {
log.Println("JWT parse error:", err)
return 0, fmt.Errorf("invalid or expired token")
}
if !token.Valid {
log.Println("JWT invalid token")
return 0, fmt.Errorf("invalid or expired token")
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
log.Println("JWT claims cast failed")
return 0, fmt.Errorf("invalid token claims")
}
uidFloat, ok := claims["user_id"].(float64)
if !ok {
log.Println("user_id missing in claims")
return 0, fmt.Errorf("user_id not found in token")
}
log.Println("Parsed user_id:", uidFloat)
// save the user_id in context for later use
if uidFloat <= 0 {
log.Println("Invalid user_id in claims:", uidFloat)
return 0, fmt.Errorf("invalid user_id in token")
}
return uint(uidFloat), nil
}
Main server file
/main.go
package main
import (
"gin-blog-api/config"
"gin-blog-api/routes"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
"log"
)
func init() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
}
func main() {
r := gin.Default()
// Connect to the database
config.ConnectDatabase()
// Register routes
routes.RegisterRoutes(r)
// Start the server
// Listen and serve on
// http://localhost:8080
r.Run(":8080")
}
Routing
routes/routes.go
package routes
import (
"gin-blog-api/handlers"
"gin-blog-api/middleware"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(router *gin.Engine) {
router.POST("/signup", handlers.Signup)
router.POST("/login", handlers.Login)
router.GET("/posts", handlers.GetPosts)
router.GET("/posts/:id", handlers.GetPostByID)
auth := router.Group("/")
auth.Use(middleware.JWTAuthMiddleware())
{
auth.POST("/posts", handlers.CreatePost)
auth.PUT("/posts/:id", handlers.UpdatePost)
auth.DELETE("/posts/:id", handlers.DeletePost)
router.POST("/logout", handlers.Logout)
}
}
middleware to protect routes
middleware/auth.go
package middlewares
import (
"gin-blog-api/utils"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func JWTAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header missing or invalid"})
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
userID, err := utils.ParseJWT(tokenString)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"})
return
}
// Attach user ID to context
c.Set("userID", userID)
c.Next()
}
}
add .env
JWT_SECRET=9ad4404902df5dde56b33bb7857e085b24b1903187d4a1b3d7c1b1526d4da4dba7de59dacb68424f52325c1e606b700653eab5c722da394d60b3d81c9de503a8
Basic testing using Postman or curl
http://localhost:8080/signup/
{
"username":"codey-singh",
"email":"ssp@yopmail.com",
"password":"qwert1234"
}
response:
{
"message": "User created successfully"
}
http://localhost:8080/login/
{
"email":"ssp@yopmail.com",
"password":"qwert1234"
}
response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJleHAiOjE3NDg0NDk0ODEsInVzZXJfaWQiOjM3MjAwMX0.
hgI8nM5RrnnAkpbOfgM4cd0hdIXI9tCVfQAnAn5ahcc",
"user": {
"email": "ssp@yopmail.com",
"id": 372001,
"username": "codey-singh"
}
}
http://localhost:8080/protected/
add: bearer + token in auth postman
response:
{
"message": "Protected content",
"user": 372001
}
Run command
go run main.go
what is in next part:
In next part i will setup a post model and functionality
In third part i will setup a user dashboard
and in final part i will proivde full code explaination
full github code will be public in third part.
Top comments (0)