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
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)
}
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)
}
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:"-"`
}
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))
}
Setting Up Database Migrations with Dbmate
Install Dbmate
go get github.com/amacneil/dbmate
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;
Running Migrations
make migrate
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)
}
}
Running the Project
With all the setup complete, you can now run your project. Open your terminal and run:
make watch
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)