View : 208

08/05/2026 06:52am

JS2GO EP.46 Building Middleware and Modular Architecture in Go and Node.js

JS2GO EP.46 Building Middleware and Modular Architecture in Go and Node.js

#Node.js

#Modular Architecture

#Go

#Middleware

Write APIs that simply “run” = easy.

Write APIs that scale, are maintainable, and survive Production = requires architecture.

 

This episode takes you from fundamentals → enterprise-level architecture.

 

By the end of this chapter, you will understand:

✔ How to build Middleware (Auth, Logging, Rate Limit)
✔ The Service / Repository Pattern
✔ Modular Architecture that is Production-ready
✔ Correct examples using Express (Node.js) & Fiber (Go)
✔ Best Practices used in real-world companies

 

⭐ 1) What Is Middleware?

 

Middleware is a function that executes before (or after) the main handler.

 

Common responsibilities include:

  • Authorization / Token verification
  • Request logging
  • Rate limiting
  • Request transformation (e.g., normalize headers)
  • Validation
  • Permission checks

 

Core execution flow (same in Go and Node.js):

Request → Middleware A → Middleware B → Handler → Response

 

Handlers should remain thin, delegating work to Middleware or the Service Layer.

 

⭐ 2) Real-world Middleware Examples (Correct and Production-safe)

 

🔹 Logging Middleware

 

Go (Fiber)

func Logging() fiber.Handler {
    return func(c *fiber.Ctx) error {
        start := time.Now()
        err := c.Next()
        fmt.Printf("[%s] %s - %d (%v)\n",
            c.Method(),
            c.Path(),
            c.Response().StatusCode(),
            time.Since(start),
        )
        return err
    }
}

 

Register:

app.Use(Logging())

 

Node.js (Express)

function logger(req, res, next) {
  const start = Date.now();
  res.on("finish", () => {
    console.log(`[${req.method}] ${req.url} - ${res.statusCode} (${Date.now() - start}ms)`);
  });
  next();
}

app.use(logger);

 

🔹 Authentication Middleware

 

Go (Fiber)

func Auth() fiber.Handler {
    return func(c *fiber.Ctx) error {
        token := c.Get("Authorization")
        if token == "" {
            return c.Status(401).JSON(fiber.Map{
                "error": "Missing Authorization header",
            })
        }
        return c.Next()
    }
}

 

Node.js (Express)

function auth(req, res, next) {
  const token = req.headers.authorization;
  if (!token) return res.status(401).json({ error: "Unauthorized" });
  next();
}

app.use(auth);

 

🔹 Basic Rate Limit (for demonstration only)

 

Go

func RateLimit() fiber.Handler {
    var mu sync.Mutex
    var count = 0

    return func(c *fiber.Ctx) error {
        mu.Lock()
        if count >= 100 {
            mu.Unlock()
            return c.Status(429).SendString("Too Many Requests")
        }
        count++
        mu.Unlock()
        return c.Next()
    }
}

 

Node.js

let count = 0;

function rateLimit(req, res, next) {
  if (count >= 100) {
    return res.status(429).send("Too Many Requests");
  }
  count++;
  next();
}

app.use(rateLimit);

 

➡ Production Note: Use Redis + Token Bucket / Sliding Window for distributed rate limits.

 

⭐ 3) What Is Modular Architecture?

 

A modular system is structured so each module is self-contained, reducing coupling and increasing maintainability.

 

Benefits:

✔ Easy to modify
✔ Easy to add features without touching other modules
✔ Easy to test
✔ Works for medium to large systems

 

📌 Production-ready Go Structure

/cmd/server/main.go
/internal
   /user
      handler.go
      service.go
      repository.go
      model.go
   /product
      handler.go
      service.go
      repository.go
/pkg
   /database

 

📌 Production-ready Node.js Structure

src/
 ├─ modules/
 │   ├─ user/
 │   │   ├─ user.controller.js
 │   │   ├─ user.service.js
 │   │   ├─ user.repository.js
 │   │   ├─ user.model.js
 │   ├─ product/
 ├─ middlewares/
 ├─ routes/
 ├─ database/
 ├─ app.js
 ├─ server.js

 

⭐ 4) Service / Repository Pattern Explained

 

Separates responsibilities across layers:

LayerResponsibility
Controller / HandlerReceive request → call service → return response
ServiceBusiness logic, validation, workflow
RepositoryDatabase operations only

 

Example in Go (Fiber)

 

Handler

func (h *UserHandler) GetUser(c *fiber.Ctx) error {
    id := c.Params("id")

    user, err := h.Service.GetUser(c.Context(), id)
    if err != nil {
        return c.Status(404).JSON(fiber.Map{"error": "User not found"})
    }

    return c.JSON(user)
}

 

Service

func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
    return s.Repo.FindByID(ctx, id)
}

 

Repository

func (r *UserRepository) FindByID(ctx context.Context, id string) (*User, error) {
    row := r.DB.QueryRow(ctx, "SELECT id, name FROM users WHERE id=$1", id)

    var u User
    if err := row.Scan(&u.ID, &u.Name); err != nil {
        return nil, err
    }

    return &u, nil
}

 

Example in Node.js (Express)

 

Controller

export const getUser = async (req, res) => {
  const user = await userService.getUser(req.params.id);
  if (!user) return res.status(404).send("User not found");
  res.json(user);
};

 

Service

export const userService = {
  getUser: async (id) => userRepository.findById(id),
};

 

Repository

export const userRepository = {
  findById: async (id) => {
    const result = await pool.query(
      "SELECT id, name FROM users WHERE id=$1",
      [id]
    );
    return result.rows[0];
  },
};

 

⭐ 5) Production Best Practices

 

✔ Controllers must stay thin
✔ Services must not query the database directly
✔ Repositories must not embed business logic
✔ Keep middleware lightweight (avoid heavy computation)
✔ Use Dependency Injection (covered in EP.47)
✔ Name files by responsibility (e.g., user.service.js)
✔ Mock repositories/services for testing

 


 

📌 Summary

 

When you combine:

  • Well-designed Middleware
  • Modular Architecture
  • Service/Repository Pattern

 

You get systems that are:

🚀 Scalable
🧱 Stable
🔍 Testable
💼 Production-ready

 

This is the architecture used by real companies and enterprise-grade backend teams.

 

🔵 Next Episode: EP.47 Dependency Injection in Go & Node.js

 

You will learn:

✔ Why DI makes systems testable
✔ Constructor-based DI in Go
✔ DI Container in Node.js
✔ How to mock services & repositories
✔ Designing a correct Dependency Graph