As developers, we use authorization libraries almost every day. Whether it's a web application, an API, or an internal tool, we often rely on packages that decide who can do what.
But I realized I had never actually built one.
So instead of using an existing library, I decided to build my own Role-Based Access Control (RBAC) library in Go using only the standard library.
This project eventually became GateKeeper v1.0.0.
Why I Built One
As a beginner in backend development, I wanted to get exposure on how to make public APIs and how to make them work under the hood.
It also made me fight my old syntax habits. Go has strict error handling, which I also learned.
I built it because I wanted to understand the engineering decisions behind public libraries. Instead of watching another tutorial, I built it myself.
Project Goals
Before writing any code, I brainstorm the architecture in my mind.
No external library used, only the standard Go library.
Keeping the public APIs simple and easy to read for developers to use.
Write tests for each and every function, no matter how small.
These constraints forced me to think more carefully about the design instead of depending on external packages.
The Core Model
Engine
├── Users
├── Roles
└── Permissions
Relationships are straightforward:
Users
|
V
Roles
|
V
Permissions
- A user can have multiple roles, and roles can have multiple permissions.
- Permissions describe access to a resource and an action.
Designing The API
One thing I learned the hard way is that API design matters more than the implementation itself.
I wanted to keep the library easy to read even without documentation.
The public API ended up looking something like this:
CreateUser()
CreateRole()
CreatePermission()
AssignRole()
AssignPermission()
Can()
DeleteUser()
DeleteRole()
DeletePermission()
RenameUser()
RenameRole()
I had to redesign the API many times before eventually coming up with the final one.
The time spent fighting the design was worth it. It taught me API design and how to think about architecture rather than just implementation.
Bugs I Found Along The Way
One of the most interesting bugs appeared when deleting roles.
Initially, my implementation simply deleted the role.
Delete Role
|
V
Role disappears
That looked correct.
Until I realized users still stored that role ID. But the role no longer existed. The system had entered an invalid state.
The correct solution wasn't just deleting the role. It was:
Delete Role
|
V
Remove role from every user
|
V
Delete Role
The same issue existed when deleting permissions.
That experience introduced me to the idea of maintaining referential integrity, something I had previously only encountered in databases.
Another bug that surprised me, and almost went unseen, was about slices.
Slices in Go point to their backing array, which means if we send a user a copy of User, that user can modify the internal state of the engine, which can cause problems.
The fix was to make defensive copies of the slices before returning them.
That bug taught me more about Go's memory model than several hours of reading documentation.
Lessons I Learned
As a beginner, you are always fighting two battles while learning: one with the programming language, and the other with the architecture you want to create.
By making this project, I not only had to think about correct, production-ready architecture, but also had to learn about Go's underlying structure.
This project made me learn many topics that are important for a beginner, like encapsulation, API design, maintaining invariants, and designing software that remains consistent after every operation.
Final Thoughts
Building software from scratch is a very different experience from using libraries.
You begin to appreciate the small design decisions that make software reliable.
GateKeeper started as an exercise in learning authorization. It ended up teaching me much more about software engineering than I expected.
If you have any suggestions or feedback, I'd be happy to hear them.
GitHub: GateKeeper
Top comments (0)