Blog banner

GoFiber + MongoDB + JWT: Scalable API with Clean Architecture

A clean and modular REST API boilerplate built with GoFiber, MongoDB, and JWT Authentication using Clean Architecture principles.

GoFiber + MongoDB + JWT: Scalable API with Clean Architecture

A clean and modular REST API boilerplate built with GoFiber, MongoDB, and JWT Authentication using Clean Architecture principles.
Photo by Juno Jo on Unsplash

Table Of Content

  • Introduction
  • Project Solution
  • Prerequisites
  • Project Struct
  • Docker
  • MongoDB
  • Sample Response
  • Todos
  • Making a conclusion

Introduction

In this article, I walk you through the development of a scalable and maintainable REST API using GoFiber, MongoDB, and JWT authentication — all structured with the Clean Architecture approach.

The goal? To create a backend that’s not just fast and functional, but also easy to extend, test, and deploy in production environments.

Whether you’re new to Go or looking to apply architectural best practices in your projects, this article covers:

  • Why Clean Architecture matters
  • How to structure your Go project for separation of concerns
  • Implementing user registration and login with secure JWT-based auth
  • Setting up MongoDB integration
  • Dockerizing your app for smooth development and deployment
  • Handling errors and responses in a unified way

The final result is a modular API you can use as a strong foundation for real-world production systems.

Let’s dive into the code and best practices!



Project Struct

The project follows Clean Architecture principles, separating responsibilities into layers like handler, service, repository, and model. Here’s a simplified breakdown with real examples:

Please change .env setting your project security !

🚀 Features

  • ✅ JWT authentication with middleware protection
  • ✅ MongoDB integration
  • ✅ Modular clean architecture
  • ✅ DTOs and model mappers
  • ✅ Dependency injection container
  • ✅ Docker support (docker-compose up --build or docker-compose down)

Project Folder

gofiber-clean-architecture/

├── main.go # Entry point of the application

├── configuration/ # Loads environment and app config
│ └── config.go

├── database/ # MongoDB setup and connection logic
│ └── mongodb.go

├── model/ # Domain models (e.g., User, APIResponse)
│ └── user_model.go

├── dto/ # Data Transfer Objects (for requests/responses)
│ └── user_dto.go

├── handler/ # Fiber route handlers (controller layer)
│ ├── auth_handler.go # Handles /auth/login and /auth/register
│ └── user_handler.go # Handles protected /user/:id endpoint

├── service/ # Business logic (use cases)
│ ├── auth_service.go
│ └── user_service.go

├── repository/ # DB access layer
│ └── user_repository.go

├── middleware/ # JWT verification logic
│ └── jwt.go

├── routes/ # Route grouping and mounting
│ └── routes.go

├── container/ # Dependency injection and wiring
│ └── container.go

Example Routes

  • GET /→ Hello Api Page
  • POST /auth/register → Registers a user
  • POST /auth/login → Returns JWT token
  • POST /user/:id → Protected route, requires valid JWT

Each layer talks only to the layer directly below it, maintaining a clean and testable separation of concerns.

project struct

✅ Unified API Response Format

All endpoints return a standardized JSON response using the ApiResponse model:

package model

type APIResponse struct {
Status string `json:"status"` // "success" or "error"
Message string `json:"message"`
Data interface{} `json:"data,omitempty"` // optional, only included for success
Error string `json:"error,omitempty"` // optional, only included for error
}

📌 Success Example

{
"status": "success",
"message": "User fetched successfully",
"data": {
"userid": "682daf193f5a8fcb18943a2e",
"username": "john_doe",
"email": "[email protected]"
}
}

❌ Error Example

{
"status": "error",
"message": "Failed to authenticate user",
"error": "Invalid email or password"
}

💡 Benefits

  • Consistent response structure across all endpoints
  • Easier error handling on frontend or client apps
  • Cleaner separation of message vs. error vs. data

How a Request Handle ?

To help you understand how a request is executed in your GoFiber Clean Architecture API — let’s walk through an example request to the /auth/login endpoint with a complete code flow explanation from entrypoint to response.

✅ Request:

POST /auth/login HTTP/1.1
Content-Type: application/json
{
"email": "[email protected]",
"password": "secret"
}

🧭 Execution Flow:

1. Request hits Fiber route (📁 handler/auth_handler.go)

const (
RegisterRoute = "/auth/register"
LoginRoute = "/auth/login"
LoginSuccessMessage = "Login successful"
RegisterSuccessMsg = "User registered successfully"
)

type AuthHandler struct {
service service.AuthService
}

func NewAuthHandler(service service.AuthService) *AuthHandler {
return &AuthHandler{service: service}
}

func (h *AuthHandler) RegisterRoutes(app *fiber.App) {
app.Post(RegisterRoute, h.RegisterUser)
app.Post(LoginRoute, h.LoginUser)
}

2. Controller/Handler function (📁 handler/auth_handler.go)

func (h *AuthHandler) LoginUser(c *fiber.Ctx) error {
var req dto.LoginRequest

// Parse body
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(model.APIResponse{
Status: "error",
Message: "Malformed JSON in request payload",
})
}

// Basic required fields check
if req.Email == "" || req.Password == "" {
return c.Status(fiber.StatusBadRequest).JSON(model.APIResponse{
Status: "error",
Message: "Both email and password are required",
})
}

// Call service to login user
user, err := h.service.LoginUser(c.Context(), req.Email, req.Password)
if err != nil {
log.Printf("Login error: %v", err)
return c.Status(fiber.StatusUnauthorized).JSON(model.APIResponse{
Status: "error",
Error: "Invalid email or password",
})
}

if user == nil {
return c.Status(fiber.StatusInternalServerError).JSON(model.APIResponse{
Status: "error",
Error: "An unexpected error occurred",
})
}

jwtToken, err := middleware.GenerateJWT(user.ID.Hex(), user.Username)

if err != nil {
log.Printf("Token generation error: %v", err)
return c.Status(fiber.StatusInternalServerError).JSON(model.APIResponse{
Status: "error",
Error: "Failed to generate token",
})
}

// Success response with user data
return c.Status(fiber.StatusOK).JSON(model.APIResponse{
Status: "success",
Message: LoginSuccessMessage,
Data: map[string]interface{}{
"userid": user.ID,
"username": user.Username,
"email": user.Email,
"token": jwtToken,
},
})
}

3. Service layer (📁 service/auth_service.go)

// LoginUser checks the email and password, returning the user if successful
func (s *authService) LoginUser(ctx context.Context, email, password string) (*model.User, error) {
user, err := s.userRepository.FindByEmail(ctx, email)
if err != nil || user == nil {
if err == mongo.ErrNoDocuments {
return nil, errors.New("invalid email or password")
}
return nil, err
}

// Compare passwords
if bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) != nil {
return nil, errors.New("invalid email or password")
}

return user, nil
}

4. Repository layer (📁 repository/user_repository.go)


func (r *userRepository) FindByEmail(ctx context.Context, email string) (*model.User, error) {
var user model.User

email = strings.ToLower(email)

err := r.collection.FindOne(ctx, bson.M{"email": email}).Decode(&user)
if err != nil {
return nil, err
}
return &user, nil
}

5. JWT Generation (📁 middleware/jwt.go)

func GenerateJWT(userID, username string) (string, error) {
jwtSecret := []byte(configuration.Get("JWT_SECRET"))
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"userid": userID,
"username": username,
"exp": time.Now().Add(72 * time.Hour).Unix(),
})
return token.SignedString(jwtSecret)
}

🔁 Response Example

{
"status": "success",
"message": "Login successful",
"data": {
"userid": "682daf193f5a8fcb18943a2e",
"username": "john_doe",
"email": "[email protected]",
"token": "eyJhbGciOiJIUzI1NiIs..."
}
}

Docker

if you are use windows docker-up.bat or

docker-compose up --build

if you are use windows docker-down.bat or

docker compose down

MongoDB

I was used MongoDB Compass for accessing data. Easily explore and manipulate your database with Compass, the GUI for MongoDB. Intuitive and flexible, Compass provides detailed schema visualizations, real-time performance metrics, sophisticated querying abilities, and much more.

https://www.mongodb.com/try/download/compass

Sample Response

📌 Success Example => GET /

GET / Request

📌 Success Example => POST /auth/register

Successful Request

❌ Error Example => POST /auth/register

Error Request

📌 Success Example => POST /auth/login

Success Request

❌ Error Example => POST /auth/login

Error Request

📌 Success Example => POST /user/@userid

Succesful Request

❌ Error Example => POST /user/@userid

Error Request

Todos

  • ✅ Centralized Error Handling: Implement a global error handler middleware for consistent and clean error responses.
  • ✅ Refactor to Follow SOLID Principles:
  • Single Responsibility (SRP): Ensure each package/component has one well-defined responsibility.
  • Open/Closed (OCP): Make modules extensible without modifying existing code.
  • Interface Segregation (ISP): Use smaller, focused interfaces for better testability.
  • Dependency Inversion (DIP): Depend on abstractions, not concrete implementations.
  • ✅ Validation Layer: Add reusable request validators using a middleware pattern.
  • ✅ DTO & Mapper Enhancements: Improve separation between internal models and external API contracts.
  • ✅ Swagger/OpenAPI Documentation: Generate and serve API documentation automatically.
  • ✅ Logging Middleware: Add structured logging for requests, responses, and errors.
  • ✅ Request ID Tracing: Correlate logs with unique request IDs for debugging.
  • ✅ Unit and Integration Tests: Cover services, handlers, and middleware with tests.
  • ✅ Config Improvements: Centralize configuration management with profiles (dev/test/prod).
  • ✅ Makefile / CLI Tooling: Add helper commands for build, lint, and test.

Making a conclusion

👨‍👦‍👦 Leave a comment, I am free for discussion with your any kind technical question.

#GoFiber #MongoDB #JWT #CleanArchitecture #GoLang #RESTAPI #BackendDevelopment #SoftwareArchitecture #WebDevelopment #OpenSource

Version 1.0.1