Recommended project structure

I am making a project with the Fiber framework and I use a folder and file structure that I use in express.js:
config - for database configuration
controllers
models - for database table definitions
routes
services - for interacting with the database
.
Can you recommend me a golang/fiber project structure? And what is the best way to learn the idioms of the language?

There is a lot of mixed advice around regarding project structure because there was never an official guide, but not long ago the Go team released a document addressing that: Organizing a Go module - The Go Programming Language so you can start from there.

For learning the idioms, a good starting point is Effective Go - The Go Programming Language

Hi, I am also working on a REST API boilerplate by Fiber, there is pre-defined modules like users, groups, todos etc. with HTMX UI. I hope that it is helpful to you.
repo: GitHub - shunwatai/fiber-rest-boilerplate: golang REST API boilerplate with supports for postgres, mariadb, sqlite & mongodb

People use various project structures. It is quite subjective. But, to be honest, I feel it doesn’t matter that much.

I used to have the same questions about these kind of thing, what is the best project structure, where should I put this logics, should I do dependency injection, does my code follow SOLID principle, does my code already do clean architecture, is my code clean, etc. I’ve been on various companies as well, and none of them use the same structure. Some put their entity in a special directory, some put all their interfaces in a single package, some has 2 packages for each module, one for the interfaces and one for the implementation.
But, you know what? Later, I realize that, all of them work. It doesn’t matter that much. All of them are maintainable, readable, testable, performant enough, and works. And when doing a hobby project, I’m starting to realize that I spend too much time structuring my codes in the beginning and I ended up refactoring a lot in the end.

So, my first advice is that: don’t get to hung up on the project structure. And try to see the bigger picture. The point of project structure, patterns, best practices, and architecture is to make your code works, maintainable, testable, performant, scalable. You can ask yourself, does your current project structure achieve those? If yes, then it’s already the best project structure.

This is a little bit OOT, but one thing that I notice about “clean” code is that usually people think in order to have a “clean” code you have to do A, B, C, D. But, it’s rarely discussed what does “clean” mean and how does A, B, C, D make it “clean”. If we do A, B, C, D but the code is not maintainable and hard to change, can we say it’s “clean”? For me personally, if the code is readable, maintainable, testable, works, performant enough, I will say it’s clean. If someone build a project containing a single file of 10K line of code, but it works, can be understand, can be maintained, performant enough, I’d say it’s clean.


Now, what I usually do is this:

  • If I use a specific framework, and that framework forces me to use a specific project structure (or make it painful not to follow them), I will follow them.
  • If I work in a team, I will follow whatever consensus in the team.
  • If I work on an existing project, I will follow whatever this project use
  • If I start from scratch on my own, I will put everyting on a single package, until I feel like I have to have multiple packages.

How do I know when I need multiple files and pacakges?

  • When it becomes hard to maintain the context. By having different package, I can use the same symbol name for different context because the package gives the context.
    • For example, a “user” in the context of authentication might be different from the “user” in the context of checkout. In auth, user is just a user. In checkout context, a user might be a seller, a buyer, or something else. Our mental model is different when talking about auth and checkout. Splitting them into different package might help our brain to work on smaller context.
  • When the maintainer getting larger. I don’t want people to edit the same file when working on different feature. It makes it hard to fix the conflict.
    • This is one of the reason why I don’t like separating my packages into roles like config, models, services. Most changes will change all those 3. But if I change it into “feature” based like auth, logistics, payments, people most likely won’t conflict if they are working on different feature. People who work on reset password flow won’t affect any files changed by people who work on new payment method for example.
  • When the compilation time becomes slower. In Go, a single package is a single compilation unit.

You know, there are a lot of successful project that use a single folder to store most of their source code like boltdb, sqlite, redis. Well, there are not a lot of reason to split them into multiple package anyway.

Another reason to have multiple packages is when you work in a team. A directory can be used for some kind of jurisdiction. For example, inside an auth directory lies all logic and all knowledge you need to understand the auth of your project. When someone want to understand the auth, it’s clear that they should go to auth directory, whereas if you have like model, config, and services, it’s unclear where should I begin to search. But again, it doesn’t matter that much because people can just ask, or search using their IDE, it’s not that hard anymore.

You can also put special rule for specific directories. For example, in github and gitlab, you can configure your repository so that each time someone edit any file inside payments directory, some people will be added as reviewer and the PR can’t be merge without those people approval. This again, another reason why I don’t like splitting the directory into model, config and services, because those directories will be owned by everyone. But again, it’s not that big a deal, there are workarounds for this.

There is also a special directory in golang called internal. If you have a directory called internal, this directory can’t be imported by other module, or other package that is not its parent. This can be useful to hide some internal logic.

please refer to this one :