Skip to Content

Project Structure

When you run gofasta new myapp, the CLI creates the following structure:

myapp/ ├── app/ # Your application code │ ├── main/main.go # Entry point │ ├── models/ # Database models (User starter included) │ ├── dtos/ # Request/response types │ ├── repositories/ # Data access layer │ │ └── interfaces/ # Repository contracts │ ├── services/ # Business logic │ │ └── interfaces/ # Service contracts │ ├── rest/ │ │ ├── controllers/ # HTTP handlers │ │ └── routes/ # Route definitions │ ├── graphql/ # Only present with --graphql │ │ ├── schema/ # GraphQL schema files (.gql) │ │ └── resolvers/ # GraphQL resolvers │ ├── validators/ # Input validation rules │ ├── di/ # Dependency injection (Google Wire) │ │ ├── container.go # Service container struct │ │ ├── wire.go # Wire build configuration │ │ ├── wire_gen.go # Generated Wire output │ │ ├── setters.go # Wire setters │ │ └── providers/ # Wire provider sets │ ├── jobs/ # Cron jobs │ └── tasks/ # Async task handlers (asynq) ├── cmd/ # CLI commands for your app │ ├── root.go # Cobra root command │ ├── serve.go # Starts the HTTP server │ ├── migrate.go # Runs database migrations │ └── seed.go # Runs database seeders ├── db/ │ ├── migrations/ # SQL migration files (up + down) │ └── seeds/ # Database seed functions ├── configs/ # RBAC policies, feature flags ├── deployments/ # Docker, CI/CD workflows, nginx, systemd ├── templates/emails/ # HTML email templates │ └── layouts/ # Email layout templates ├── locales/ # Translation files (i18n YAML) ├── testutil/mocks/ # Test mocks ├── config.yaml # Application configuration ├── compose.yaml # Docker Compose (app + PostgreSQL) ├── Dockerfile # Production container image ├── Makefile # Development shortcuts ├── gqlgen.yml # GraphQL code generation config (only present with --graphql) ├── .air.toml # Air hot reload configuration └── .env.example # Example environment variables

Key Directories

app/ — Your Application Code

This is where you spend most of your time. It follows a layered architecture — one directory per technical concern (models, DTOs, repositories, services, controllers, routes), with each resource contributing one file to each directory:

Request → Controller → Service → Repository → Database

Why layered, not feature-module

This layout is a deliberate choice, not an oversight. The main alternative — feature modules (also called vertical slice architecture, package by feature, or DDD modules), where each business domain lives in its own directory containing its controller, service, repository, and model together — has real merits for large applications. Gofasta ships with a layered layout because:

  • It matches the mental model most backend engineers already have. The “controller → service → repository” pipeline is the dominant pattern in mainstream MVC-style backends, and developers coming from that background find a layered layout immediately familiar.
  • It keeps small projects small. For an app with 3–8 resources, flat layer directories are simpler to navigate than nested feature modules.
  • It aligns with Go’s stdlib aesthetic of small, focused packages (net/http, encoding/json, database/sql) rather than large multi-concern packages.
  • Cross-cutting concerns have a natural home. Shared DTOs, middleware, and validators don’t have to be forced into a shared/ or common/ grab-bag.

For projects that grow past ~15 resources or multiple teams, feature-module structure scales better. The Restructuring to feature modules section below explains how to migrate — the change is localized and the pkg/* library imposes zero assumptions about which layout you use.

  • models/ — GORM database models. Each model embeds models.BaseModelImpl from the gofasta library to get standard fields (ID, timestamps, soft delete, versioning).
  • dtos/ — Data Transfer Objects for request parsing and response formatting. Keeps your API contract separate from your database models.
  • repositories/ — Data access layer. Each repository has an interface in interfaces/ and an implementation that uses GORM.
  • services/ — Business logic layer. Receives DTOs, calls repositories, applies rules. This is where your domain logic goes.
  • rest/controllers/ — HTTP handlers. Parse requests, call services, return responses. No business logic here.
  • rest/routes/ — Route definitions. Maps HTTP methods and paths to controller methods.
  • graphql/ — Schema files (.gql) and resolvers. gqlgen generates resolver stubs from your schema. This directory is only present when the project was created with gofasta new --graphql.
  • validators/ — Custom validation rules using go-playground/validator/v10 struct tags.
  • di/ — Google Wire dependency injection. wire.go defines how to build the container; wire_gen.go is the generated output. Tools are registered via go mod edit -tool (Go 1.24+ tool dependencies) and invoked via go tool wire, go tool gqlgen, etc.
  • jobs/ — Cron jobs for scheduled background work.
  • tasks/ — Async task handlers using hibiken/asynq.

cmd/ — Project CLI Commands

Your project has its own CLI built with Cobra:

  • serve.go — Starts the HTTP server. Initializes the DI container, registers routes, and listens on the configured port.
  • migrate.go — Runs database migrations up or down.
  • seed.go — Runs database seeders. Useful for populating test data.

db/ — Database Files

  • migrations/ — SQL migration files in pairs: 000001_create_users.up.sql and 000001_create_users.down.sql. Run with gofasta migrate up and gofasta migrate down.
  • seeds/ — Go functions that insert test/default data into the database.

deployments/ — Infrastructure

Configuration for the deployment target gofasta natively supports: a Linux VPS running Docker or systemd, reached over SSH via gofasta deploy.

  • ci/ — GitHub Actions workflow templates (test, release, deploy-vps). These ship as inert YAML; copy them into .github/workflows/ to activate — see the deployment guide for details.
  • docker/ — Production compose file used by gofasta deploy, plus a development Dockerfile with Air for hot reload.
  • nginx/ — Reverse proxy configuration with TLS termination.
  • systemd/ — Service unit and reference deploy script used by the binary deploy method.

config.yaml — Application Configuration

Central configuration file for your app:

app: name: myapp port: 8080 env: development database: driver: postgres host: localhost port: 5432 name: myapp_db user: postgres password: postgres jwt: secret: your-secret-key expiration: 24h redis: host: localhost port: 6379

Settings can be overridden with environment variables using the prefix GOFASTA_ with the pattern GOFASTA_SECTION_KEY (e.g., GOFASTA_DATABASE_HOST=db).

What You Write vs. What the Library Provides

You writeThe gofasta library provides
app/models/product.model.gopkg/models — BaseModelImpl with UUID, timestamps, soft delete
app/services/product.service.gopkg/validators — Input validation, pkg/utils — Pagination
app/rest/controllers/product.controller.gopkg/httputil — Bind, Handle, JSON responses
app/rest/routes/product.routes.gopkg/middleware — CORS, logging, rate limiting
config.yamlpkg/config — LoadConfig(), SetupDB()
cmd/serve.gopkg/health — Health check endpoints

Restructuring to feature modules

The layered default fits most projects, but for larger codebases — many resources, multiple teams, or plans to extract microservices later — feature modules (also called vertical slice architecture or package by feature) scale better. The idea: each business domain gets its own directory containing everything that feature needs.

What the target layout looks like

app/ ├── user/ │ ├── model.go # GORM model │ ├── repository.go # Repository impl │ ├── repository_iface.go # Repository interface │ ├── service.go # Business logic │ ├── service_iface.go # Service interface │ ├── controller.go # REST handlers │ ├── routes.go # chi.Router registration │ ├── dtos.go # Request/response types │ ├── validators.go # Feature-specific validators (optional) │ └── service_test.go ├── product/ # Same shape, different domain │ ├── model.go │ ├── repository.go │ ├── service.go │ ├── controller.go │ ├── routes.go │ └── dtos.go ├── invoice/ # Same shape, different domain ├── shared/ # Cross-cutting types: common DTOs, shared validators ├── di/ # Stays as a top-level concern ├── jobs/ # Stays layered — cron jobs don't belong to one domain └── tasks/ # Stays layered — async tasks typically cross domains

Benefits you get

  • Change locality. Add a Subscription resource → create one directory. Under layered, that same addition edits models/, dtos/, repositories/, services/, controllers/, routes/, validators/, and tests — eight places for one feature.
  • Package-private encapsulation. Because Go hides lowercase identifiers at the package boundary, a user/ package can expose user.Service and user.Controller while keeping repository, mapper, and internal types unexported. Layered structure forces most types public because layers reference each other across package boundaries.
  • Readable ls app/. user/ product/ invoice/ subscription/ notification/ tells you what the app does. Layered controllers/ services/ repositories/ tells you only how it’s built.
  • Easier extraction. One feature = one candidate microservice later. No multi-directory hunt.

Drawbacks to weigh

  • More ceremony for small apps. Three resources in three folders with six files each is heavier than six flat folders with three files each.
  • Cross-feature references need discipline. user referencing invoice can create import cycles if not careful. DDD’s rule is that only aggregate roots cross module boundaries, and usually through an interface defined in the consumer side.
  • Unfamiliar to engineers coming from layered MVC backends. Feature modules feel like package-by-feature / vertical-slice codebases; flat layers feel like the layered MVC backends most engineers learn first. Team background matters.

Migration recipe

Starting from a layered Gofasta scaffold, moving one resource at a time keeps the app compilable throughout:

  1. Create the feature directory — e.g., mkdir app/user.

  2. Move and rename filesgit mv each layer’s user file into the new directory, and rename to drop the redundant prefix:

    git mv app/models/user.model.go app/user/model.go git mv app/dtos/user.dtos.go app/user/dtos.go git mv app/repositories/user.repository.go app/user/repository.go git mv app/repositories/interfaces/user_repository.go app/user/repository_iface.go git mv app/services/user.service.go app/user/service.go git mv app/services/interfaces/user_service.go app/user/service_iface.go git mv app/rest/controllers/user.controller.go app/user/controller.go git mv app/rest/routes/user.routes.go app/user/routes.go git mv app/validators/user.validators.go app/user/validators.go
  3. Change each moved file’s package declaration from package models, package services, package controllers, etc. to package user.

  4. Update imports in the moved files. Internal references that used to cross packages (e.g., controllers.UserController calling services.UserService) collapse to in-package references (Service instead of services.UserService). Types like models.User become just User.

  5. Update external callersapp/di/providers/user.go.tmpl, cmd/serve.go, and app/rest/routes/index.routes.go need their imports changed from app/rest/controllers, app/services, etc. to app/user.

  6. Regenerate Wiregofasta wire. The dependency graph didn’t change, only the import paths, so generated output stays correct after imports are updated.

  7. Compile + testgo build ./... catches any missed import, then go test ./....

  8. Delete the now-empty app/models/, app/dtos/, etc. directories for the migrated resource.

  9. Repeat for each resource.

What the CLI does (and doesn’t) do

As of today, gofasta g scaffold Product name:string price:float generates into the layered layout — that’s the only output shape the generator supports. If you’ve restructured to feature modules, you have two options:

  • Run gofasta g scaffold, then move the generated files into your feature directory with the migration recipe above. Takes ~30 seconds per resource.
  • Stop using gofasta g scaffold for feature-module projects and write the files by hand. The eight-file template is predictable enough to copy-paste from an existing resource.

A --layout=feature flag on gofasta new and the generators is on the wishlist — see the white paper’s roadmap — but not yet implemented. If you’d find it useful, open an issue.

When to migrate (and when not to)

Migrate when:

  • The app has ~15+ resources and growing.
  • Multiple teams are contributing and stepping on each other in shared layer directories.
  • You’re planning to extract specific domains into separate services later.
  • The layered structure is creating visible coupling problems (e.g., user.service knows about invoice.dtos).

Stay layered when:

  • The app has fewer than ~10 resources.
  • The team is mostly coming from layered MVC backends and the flat layout matches their mental model.
  • You haven’t felt real pain from the current structure — speculative refactoring is the most expensive kind.

Both layouts are legitimate. The Go community is genuinely split on this — Ben Johnson’s “Standard Package Layout”, the package-by-feature / vertical-slice camp, and the traditional layered camp are all actively defended in production codebases. Pick the one that fits your team and your current scale; you can migrate later if the trade-offs shift.

Next Steps

Last updated on