Go-mono-project
Go-Mono-Project is a Go REST APIs rewrite of my original Spring Boot–based monitoring system. It retains core features like project tracking, RBAC, and department-level management, while improving performance, structure, and maintainability through a cleaner and more modular Go backend.
Overview
What was built
A Go-based REST API rewrite of an original Spring Boot project monitoring system. Handles project tracking, role-based access control (RBAC), and department-level management for internal teams.
Why it was built
The original Spring Boot implementation carried JVM startup overhead and verbose boilerplate that slowed iteration. Rewriting in Go produced a leaner binary, lower memory footprint, and forced a clean architectural rethink: layered handler/service/repository instead of an organically grown monolith.
Architecture & Design
Single Go binary exposing a JSON REST API. Requests pass through middleware (JWT auth, logging, CORS) before reaching domain handlers. Handlers delegate to services which enforce RBAC and call repositories that execute parameterised SQL via pgx.
Any REST client (Postman, frontend, or service)
JWT auth, request logging, CORS
Route parsing, request binding, response shaping
Business logic, RBAC enforcement, validation
Parameterised SQL via pgx, data mapping
Persistent storage for projects, users, departments
Challenges & Solutions
Problem
Internal teams lacked a centralised way to track project progress across departments. The existing Spring Boot codebase had grown without a clean separation of concerns, making it slow to change and expensive to run for the workload it handled.
Constraints
- API contract must remain backward-compatible with the existing frontend
- RBAC logic must match the original permission model exactly
- No external auth service; authentication handled in-process
Approach
Adopted a layered Go architecture (handler, service, repository) with constructor-based dependency injection. Each domain is an isolated package. RBAC is enforced at the service layer, not in middleware, so permission checks stay adjacent to business logic and are testable without HTTP context. PostgreSQL access uses pgx with parameterised queries throughout.
Translating Spring Boot's annotation-driven DI to idiomatic Go without a framework.
Constructor injection throughout: each layer receives dependencies as parameters. A single wiring function in main.go composes the full dependency graph explicitly.
No reflection or hidden magic. Fully traceable dependency graph, straightforward to mock in tests.
Replicating the original per-role, per-resource RBAC permission matrix in Go.
Defined a Permission type and a role → []Permission map in a dedicated auth package. Service functions check permissions before any mutation executes.
RBAC is centrally defined, consistently enforced, and testable in isolation without HTTP context.
Preventing SQL injection without an ORM to enforce parameterised queries.
Mandated pgx parameterised queries across all repository functions, with no raw string formatting of user input.
Zero SQL injection surface. Type mismatches between Go and Postgres types caught at compile time.
Go over Java / Spring Boot
Self-contained binary, near-instant startup, and lower memory footprint than JVM. Forces explicit dependency wiring with no annotation magic to hide coupling.
Loses Spring Security, Hibernate, and the broader JVM ecosystem. Auth and data-access layers must be composed from smaller, explicit libraries.
pgx over database/sql + GORM
pgx is the highest-performance PostgreSQL driver for Go and exposes Postgres-native types. Explicit SQL stays readable and debuggable without ORM abstraction.
More verbose than GORM for simple CRUD. Schema migrations must be managed separately (e.g., golang-migrate).
Layered packages over flat structure
Enforces that HTTP concerns don't bleed into business logic, and business logic doesn't bleed into SQL. Each layer is testable independently.
More files and indirection up front. Justified only once the endpoint count grows beyond trivial size.
Results & Learnings
All original monitoring features rebuilt in Go with a clean layered structure
Stateless JWT auth and RBAC enforced across all resource types
Parameterised SQL throughout with no SQL injection surface
Key Lessons
- Constructor injection in Go is sufficient; no DI framework needed
- RBAC belongs in the service layer, not middleware, for testability and locality
- Explicit SQL is more maintainable than ORM magic when the team knows SQL
Future Improvements
- Add golang-migrate for reproducible schema migrations
- Integration test suite covering all RBAC permission scenarios
- OpenAPI spec generated from handler signatures
What I'd Do Differently
- Define the OpenAPI contract before writing handlers to prevent response shape drift
- Set up CI on day one; retrofitting it later costs more than the initial setup