Folder conventions

internal/, packages/shared, apps/ — the rules and the rationale.

7 mineasy

Grit has one place for everything. That sounds like a constraint — it's actually a superpower. Every team member, every AI agent, every future-you knows where to look without thinking. This lesson covers the rules.

The four big places

  • apps/api/cmd/ — entry points (just server/main.go usually)
  • apps/api/internal/ — everything that's not exported. The actual code lives here.
  • apps/web/ + apps/admin/ — Next.js apps. Standard App Router shape.
  • packages/shared/ — Zod + TS types. Imported by web and admin.

internal/ — the seven sub-folders that matter

apps/api/internal/
internal/
├── config/ Loads .env into a typed Config struct.
├── database/ Postgres connection + AutoMigrate.
├── handlers/ HTTP handlers — thin, one per resource.
├── middleware/ auth, CORS, security, request ID, etc.
├── models/ GORM struct definitions.
├── routes/ routes.go — mounts every handler on a route.
└── services/ Business logic — called by handlers.

The flow of a request

HTTP request
→ middleware (auth, CORS, security)
→ routes/routes.go picks the handler
→ handlers/x.go parses input, calls services
→ services/x.go does the work, calls models
→ models/x.go is the GORM struct used to read/write DB
→ response shaped by handler, sent back
The rule: handlers are thin, services are thick. Handlers should be ~20 lines (parse → call service → return). Anything else — DB queries, validation, side effects — belongs in a service.

packages/shared/ — the type bridge

packages/shared/
packages/shared/
├── src/
│ ├── schemas/ Zod schemas (used to validate API calls)
│ ├── types/ TS types (generated by grit sync from Go structs)
│ └── constants/ Route constants, enums, shared values.
└── package.json Imported as @workspace/shared by web + admin

What does NOT go in internal/

  • Tests — they live alongside the file they test: handlers/user.gohandlers/user_test.go.
  • Migrations — Grit uses GORM AutoMigrate by default. No internal/migrations/ folder.
  • Static assets — frontend assets live in apps/web/public/ or apps/admin/public/.

Quick check

You need to add a function that validates a phone number — used by signup AND by the admin panel's user form. Where does it go?

Try it

You need to add a NotificationService that sends push notifications when a user's order ships. Decide where each piece goes — write it in notes.md:

  1. The struct + methods that talk to Expo Push?
  2. The DB row that tracks "was this notification sent?"
  3. The HTTP endpoint to trigger a test push from the admin panel?
  4. The TypeScript type for the admin form's payload?

What's next

Folders sorted. Next — naming. snake_case for Go files, kebab-case for TS, plural for routes. Same logic: one rule, applied everywhere.

Spot a typo? Have an idea?

Help us improve this lesson. One click opens a GitHub issue with the lesson URL pre-filled — suggest clearer wording, report a bug, or request more depth. The course keeps improving thanks to learners like you.

Suggest an improvement on GitHub