Golang API Structure for Beginners



This content originally appeared on DEV Community and was authored by Rob Myers

If you are new to Go, or just want to get started with Go, I am sure you have asked yourself this at least once.

How do I structure my go project?

That question brings nightmares to new go devs or ones that have lots of experience with other languages like Java. Go likes to do things its way, so your previous experience is not helpful. Hell, they probably are an antipattern in Go world.

So what do you do? You go online and type “how to structure web app project in go” and then you get 100s of different opinions. The confusion never ends…

Don’t worry, my friend, the rescue is here. In this guide, I’ll show you a battle-tested Go API structure that combines Clean Architecture principles, Domain-Driven Design, and real-world maintainability.

What’s a good project structure?

The key elements of a good project structure are:

  • Separates core business logic from external concerns (databases, APIs, frameworks).
  • Groups related features together so you’re not hunting through unrelated folders.
  • Enforces a logical flow — from incoming HTTP request to business logic to data persistence.

Context switching is expensive. That’s why if your services follow the same logical structure, you’ll be able to work on any of them and be productive in no time.

Step-by-Step: Beginner-Friendly Golang API Folder Layout

Let’s walk through the layout piece by piece so you know exactly how it works and why it exists.

user-service/
├── cmd/                 # Entry point(s)
├── internal/            # Core application logic
├── infrastructure/      # External systems
├── Makefile             # Simple build/run commands
└── openapi.yaml         # API contract documentation

Looks simple enough, right?

Let’s see whats going on here:

  • cmd/ — Your app’s front door. This is where your HTTP server (or other apps like workers/CLI tools) live.
  • internal/ – Your private workspace. Anything in here is for this project only.
  • infrastructure/ – Where external systems (like databases or third-party APIs) live—kept separate so swapping or upgrading is smooth.
  • Makefile — One command to make run or make test. Way more satisfying than long go commands. openapi.yaml — Your API’s contract. Great for docs, client SDKs, or keeping consistent behavior when working with others.

2. cmd/ – Your Application Entry Points

The cmd/ directory is the front door to your application. Each subfolder here starts an independent instance of your app. The app will share its internal logic, but it’ll be accessed in a different way.

This is where you keep entry points—thin layers that:

  • Load configuration.
  • Initialize dependencies.
  • Start a specific type of application (HTTP server, gRPC server, event consumer, cron job, etc.)
cmd/
├── http/               # Main REST/HTTP API
│   └── main.go
├── grpc/               # gRPC API server
│   └── main.go
├── events/             # Event consumer (Kafka, NATS, RabbitMQ)
│   └── main.go
└── worker/             # Background jobs/cron tasks
    └── main.go

You want to keep the entry points as simple as possible, example:

func main() {
    cfg := config.Load()
    deps := dependencies.Init(cfg)
    server := http.NewServer(deps)
    server.Start()
}

Load the config, intitalize dependencies, and start the transport layer, that’s it. KISS (Keep It Stupid Simple)

Many new Go developers start with one main.go and then try to fit everything together: HTTP, gRPC, background jobs, and event processing all into the same process. This leads to bloated code and makes scaling harder.

But if you work on a solo project, and crazy scaling is none of your concerns, then that’s fine.

infrastructure/ – Where External Worlds Live

If internal/ is your private home, then infrastructure/ is the garage, driveway, and cables connecting you to the outside world.

Here we’ll stuff all the code that interacts with external parts of our system. DB, Cache, Event Brokers, External APIs and anything else you can think of.
If you have to call somebody else to handle the job, it goes here.

infrastructure/
├── db/                 # Database layer
│   ├── migrations/     # SQL schema changes
│   ├── models/         # DB-specific structs with tags
│   └── repos/          # Repository implementations
├── cache/              # Caching layer (Redis, Memcached)
│   └── redis.go
├── events/             # Event broker clients (Kafka, NATS, RabbitMQ)
│   ├── publisher.go
│   └── consumer.go
├── externalapi/        # Third-party API integrations
│   └── payment_client.go
└── storage/            # File/object storage (S3, GCS)
    └── s3_client.go

Why do you want to do that?

  • Isolation from your business logic – Your core modules shouldn’t know if data comes from Postgres or Mongo, Redis or Memcached, Kafka or NATS.
  • Easy swaps – Change providers or services without rewriting your whole codebase.
  • Testability – Replace real implementations with in-memory mocks during tests.

internal/ – Where your app lives

It’s the main home for your application logic, and Go’s compiler makes it private, so no one outside your repo can import it.

This is exactly the structure we use in one of the microservices in the Build Microservices in Go course, so you’re looking at a proven, real-world example.

/internal/
  ├───config/
  │   ├───config.go
  │   └───utils.go
  ├───dependencies/
  │   └───dependencies.go
  ├───http/
  │   ├───server.go
  │   └───health/
  │       ├───router.go
  │       └───useCases/
  │           └───healthReady/
  │               └───controller.go
  ├───models/
  │   └───user.go
  └───modules/
      ├───auth/
      │   ├───module.go
      │   └───useCases/
      │       ├───login/
      │       │   ├───controller.go
      │       │   ├───dto.go
      │       │   └───service.go
      │       └───registerUser/
      │           ├───controller.go
      │           ├───dtos.go
      │           ├───events.go
      │           └───service.go
      └───profile/
          ├───module.go
          └───useCases/
              ├───getProfile/
              │   ├───controller.go
              │   ├───dto.go
              │   └───service.go
              └───updateProfile/
                  ├───controller.go
                  ├───dto.go
                  └───service.go

This is an example structure of /internal folder of our user service.

Now, let’s break it down, what’s inside each folder.

config/ — Centralized Configuration

This is where you define how your app loads environment variables, config files, or any setup parameters.

  • config.go holds your main config structs and parsing logic.
  • utils.go might contain helpers for validating config or converting env values.

Having a dedicated config package keeps your settings organized and easy to maintain.

dependencies/ — Dependency Injection Hub

Dependency injection can feel complex at first, but having a single place to wire all your dependencies makes your app easier to manage and test.

  • dependencies.go is where you create and inject instances for DB connections, external clients, validators, and repositories.
  • This means your main.go stays clean — it just calls into this package to get a ready-to-go app.

http/ — HTTP Server Setup & Routes

This folder is responsible for setting up your HTTP server and routing incoming requests to the right handlers.

  • server.go bootstraps the HTTP server, sets up middleware, and starts listening.
  • The subfolder health/ is a great example of how to organize feature-specific routes (in this case, health check endpoints), including their own router and use cases.

This keeps HTTP concerns separate from your business logic inside modules/.

models/ — Your Domain Models

Here you define your plain Go structs representing core business entities — for example, user.go holds your User model.

These structs don’t know anything about the database or HTTP — they’re just pure representations of data in your domain. This separation makes your code easier to test and refactor.

modules/ — Organize by Business Features

This is where your actual business features live, grouped by domain for clarity and scalability.

Each module (like auth/ or profile/) has its own:

  • module.go for wiring the module, registering routes, and initializing use cases.
  • useCases/ folder that contains all the individual workflows related to that feature, each with their own clear separation of concerns:
    • controller.go for HTTP request handling.
    • dto.go (or dtos.go) for data transfer objects to validate and transfer data.
    • service.go for the business logic itself.
    • Optionally, events.go for publishing domain events triggered by this use case.
    • Or even their own repo if that’s necessary.

For example, the auth module’s login use case handles everything related to logging users in — controllers translate HTTP requests, services do the core work, and DTOs handle validation.

Why this structure works so well for beginners and pros alike

  • Clear boundaries: You always know where to add a new feature or fix a bug.
  • Scalable: Adding a new domain or use case is as simple as creating a new folder inside modules/.
  • Testable: Isolate business logic in services and DTOs, separate from HTTP handlers.
  • Maintainable: Group related code together so your codebase doesn’t turn into a mess.

Ready to build your own scalable, maintainable Go APIs?

This folder structure isn’t just theory — it’s the foundation of real-world microservices I teach in my Build Microservices in Go course. If you want hands-on guidance, step-by-step project, and expert tips on building production-ready APIs and microservices, check it out.

Start structuring your Go projects like a pro and take your skills to the next level today!


This content originally appeared on DEV Community and was authored by Rob Myers