DEV Community

Sheena Zien
Sheena Zien

Posted on

A Final Step, Connect Database And The Seeder (Part 3)

Hey there! Welcome back to our Golang API series. In Part 2, we set up the basic structure and routing for our API. Now, it's time to level up by adding JWT-based authentication and database migrations. Let's get started!

Setting Up JWT Authentication

First, let's install the JWT package:

go get github.com/gofiber/contrib/jwt
Enter fullscreen mode Exit fullscreen mode

Update Router to Use JWT Middleware

Next, we'll update our router to include routes that require JWT authentication. Update router/router.go:

package router

import (
    "your_project/pkg/controllers"
    "your_project/pkg/middleware"

    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/cors"
)

func SetupRouter(app *fiber.App) {
    app.Use(cors.New())

    // Guest route
    app.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Hello, World!")
    })
    loginController := controllers.LoginController{}
    app.Post("/login", loginController.Login)

    // The middleware
    p := config.Config("APP_SCREET")
    app.Use(jwtware.New(jwtware.Config{
        SigningKey: jwtware.SigningKey{Key: []byte(p)},
    }))

    // Protected route
    app.Get("/profile", loginController.Profile)
}
Enter fullscreen mode Exit fullscreen mode

Create The Controller

Let's create a LoginController that has a Login and Profile function in pkg/controllers/login-controller.go:

package controllers

import (
    "your_project/db"
    "your_project/pkg/models"
    "your_project/pkg/utils"
    "github.com/gofiber/fiber/v2"
    "golang.org/x/crypto/bcrypt"
)

type LoginController struct {
}

func (s LoginController) Login(c *fiber.Ctx) error {
    var input struct {
        Email    string `json:"email"`
        Password string `json:"password"`
    }
    if err := c.BodyParser(&input); err != nil {
        return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
    }

    var user models.User
    if err := db.DB.Where("email = ?", input.Email).First(&user).Error; err != nil {
        return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid credentials"})
    }

    if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(input.Password)); err != nil {
        return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid credentials"})
    }

    token, err := utils.GenerateJWT(user.ID)
    if err != nil {
        return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
    }

    return c.JSON(fiber.Map{"token": token})
}

func (s LoginController) Profile(c *fiber.Ctx) error {
    userID := c.Locals("user_id").(uint)
    var user models.User
    if err := db.DB.First(&user, userID).Error; err != nil {
        return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
    }

    return c.JSON(user)
}
Enter fullscreen mode Exit fullscreen mode

Create User Model

Ensure your user model has the necessary fields for registration and authentication. Update pkg/models/user.go:

package models

import "gorm.io/gorm"

type User struct {
    gorm.Model
    Name     string `json:"name"`
    Email    string `json:"email" gorm:"unique"`
    Password string `json:"-"`
}
Enter fullscreen mode Exit fullscreen mode

Create JWT Utility

We'll create a utility function to generate JWT tokens. Create pkg/utils/jwt.go:

package utils

import (
    "time"

    "github.com/golang-jwt/jwt"
    "your_project/config"
)

func GenerateJWT(userID uint) (string, error) {
    secret := config.Config("APP_SECRET")
    claims := jwt.MapClaims{
        "user_id": userID,
        "exp":     time.Now().Add(time.Hour * 72).Unix(),
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString([]byte(secret))
}
Enter fullscreen mode Exit fullscreen mode

Setting Up Database Migrations with Dbmate

Install Dbmate

go get github.com/amacneil/dbmate
Enter fullscreen mode Exit fullscreen mode

Create Migration File

Create a migration file with make create-migration command then you can add the file name users this is will be generate timestamp_users.sql inside db/migrations directory, then you can the sql code with the following content:

-- migrate:up
CREATE SEQUENCE users_id_seq START WITH 1;
CREATE TABLE "users"(
    "id" int8 NOT NULL DEFAULT nextval('users_id_seq'::regclass),
    "email" TEXT,
    "password" TEXT,
    "created_at" timestamptz,
    "updated_at" timestamptz,
    "deleted_at" timestamptz,
    PRIMARY KEY("id"),
    UNIQUE("email")
);

-- migrate:down
DROP TABLE "users";
DROP SEQUENCE users_id_seq;
Enter fullscreen mode Exit fullscreen mode

Running Migrations

make migrate
Enter fullscreen mode Exit fullscreen mode

Create Seeder File

Let's create a seeder to populate our database with initial data, update your main.go

func main() {
    // other code ...
    app := fiber.New()
    db.ConnectDB()
    Seed()
   // other code ...
}

func Seed() {
    password, err := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost)
    if err != nil {
        log.Fatalf("Failed to hash password: %v", err)
    }

    user := models.User{
        Name:     "Admin",
        Email:    "admin@example.com",
        Password: string(password),
    }

    if err := db.DB.Create(&user).Error; err != nil {
        log.Fatalf("Failed to seed user: %v", err)
    }
}
Enter fullscreen mode Exit fullscreen mode

Running the Project

With all the setup complete, you can now run your project. Open your terminal and run:

make watch
Enter fullscreen mode Exit fullscreen mode

This will start the Fiber server, and you can now access your API at http://localhost:3000/.
And we have api in our application is:

We've completed the last part 🚀

Stay tuned on this repository where we'll continue to enhance our boilerplate, add more features, and improve our project structure even further. Happy coding!

Top comments (0)