How do you structure API projects?

I am coming from C#/.NET, and since it heavily relies on Namespace/Classes + DI, the similar fashion of project structure might not be intuitive in Go. I was wondering how do you guys structure your API projects.

I am currently going with something similar to the following, but I haven’t deployed any production app so it is all very amateurish.

- cmd/
  - api/
- internal/
  - api/
    - handlers/
  - db/
    - models/
    - crud/ // Not needed with GORM
  - services/
    - serviceA/
    - serviceB/
    - ...
  - utils/
- docker-compose.yml
- Dockerfile
- Makefile
- go.mod
- go.sum

What is the best way to give access to db to the services? Should I have a struct and then have db.DB or gorm.DB as a field?

Well, my main bit of advice is: avoid the golang-standards organization and advice therein. This looks like a good starting point to me with the caveat that you should only add things as you need them (like I never just go and create a bunch of folders as the first course of action on a new app myself). I think this is an excellent post on package names and worth a read:

Specifically this piece of advice is something I try (with varying degrees of success) to adhere to:

Don’t steal good names from the user. Avoid giving a package a name that is commonly used in client code. For example, the buffered I/O package is called bufio, not buf, since buf is a good variable name for a buffer.

And another good article:

OK - if I am nitpicking here’s some minor feedback:

  • I think /db/crud/ might be better suited as generalized to /db/queries because CRUD implies simple (Create, Read, Update, Delete) queries only. In most projects you’re going to have other queries.
  • I think having each service as its’ own package could be overkill and more of a .NET way of doing things. What do your services do? Why are they a “service” and not just something like /internal/auth? Are your services structs like type AuthService and are you managing lifetime (like is that a singleton/scoped to context/whatever?). If not, I’d say it’s probably better to just go with something like /internal/auth.
  • You could make the argument that utils is too generic. I’m assuming, though, that you will create utilities as needed in there and it’s mostly a catch-all for “stuff that doesn’t quite warrant its’ own top-level folder or a package”. The official naming guide above says “Packages named util , common , or misc provide clients with no sense of what the package contains”. But - I think it’s fine as long as you make a note about what is contained (more on that later).

Circling back on things like util and documentation: I find it helpful even if a package is going to be used internally to provide top-level package documentation. For example if you’re keeping util against the official naming guidelines you might provide a reason like this:

/* 
Package util contains all miscellaneous items that might not warrant
their own package. It is against the official naming standards (#YOLO) but 
we have found utility in it (no pun indended). Feel free to refactor items 
out of this package as they grow and justify their own packages.

Further reading on why this could potentially be considered a bad package name:
https://go.dev/blog/package-names#bad-package-names
*/
package util

That’s the type of thing that signals to future developers “yes, I HAVE thought about this package name and it is there for a reason”.

In summary: you did a really good job and if I came into a project structure like this I’d say it’s idiomatic and I would understand the intent by reading your project structure alone.

2 Likes